├── .gitignore ├── images ├── icon.png ├── bookmarks-folder-icon.png ├── numbered-bookmarks-list.gif ├── numbered-bookmarks-toggle.png ├── numbered-bookmarks-commands.png ├── numbered-bookmarks-list-from-all-files.gif ├── vscode-numbered-bookmarks-logo-readme.png ├── numbered-bookmarks-list-from-all-files-multi-root.gif └── bookmark.svg ├── .gitmodules ├── walkthrough ├── customizedNumberedBookmark.png ├── toggle.md ├── toggle.nls.pt-br.md ├── workingWithRemotes.md ├── workingWithRemotes.nls.pt-br.md ├── customizingAppearance.md ├── customizingAppearance.nls.pt-br.md ├── navigateToNumberedBookmarks.md ├── navigateToNumberedBookmarks.nls.pt-br.md ├── inspiredInDelphiButOpenToOthers.md └── inspiredInDelphiButOpenToOthers.nls.pt-br.md ├── .vscode ├── bookmarks.json ├── settings.json ├── numbered-bookmarks.json ├── extensions.json ├── tasks.json └── launch.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── FUNDING.yml ├── workflows │ └── main.yml └── copilot-instructions.md ├── .vscodeignore ├── tsconfig.json ├── l10n ├── bundle.l10n.json └── bundle.l10n.pt-br.json ├── src ├── core │ ├── bookmark.ts │ ├── constants.ts │ ├── container.ts │ ├── file.ts │ ├── operations.ts │ └── controller.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── index.ts │ │ └── extension.test.ts ├── utils │ ├── revealLocation.ts │ ├── reveal.ts │ └── fs.ts ├── whats-new │ ├── commands.ts │ └── contentProvider.ts ├── quickpick │ └── controllerPicker.ts ├── storage │ └── workspaceState.ts ├── decoration │ └── decoration.ts ├── sticky │ ├── stickyLegacy.ts │ └── sticky.ts └── extension.ts ├── webpack.config.js ├── package.nls.pt-br.json ├── package.nls.json ├── README.md ├── CHANGELOG.md ├── package.json └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | dist 6 | images/bookmark?*.svg -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/icon.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vscode-whats-new"] 2 | path = vscode-whats-new 3 | url = https://github.com/alefragnani/vscode-whats-new.git -------------------------------------------------------------------------------- /images/bookmarks-folder-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/bookmarks-folder-icon.png -------------------------------------------------------------------------------- /images/numbered-bookmarks-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/numbered-bookmarks-list.gif -------------------------------------------------------------------------------- /images/numbered-bookmarks-toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/numbered-bookmarks-toggle.png -------------------------------------------------------------------------------- /images/numbered-bookmarks-commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/numbered-bookmarks-commands.png -------------------------------------------------------------------------------- /walkthrough/customizedNumberedBookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/walkthrough/customizedNumberedBookmark.png -------------------------------------------------------------------------------- /.vscode/bookmarks.json: -------------------------------------------------------------------------------- 1 | { 2 | "bookmarks": [ 3 | { 4 | "fsPath": "$ROOTPATH$\\extension.ts", 5 | "bookmarks": [ 6 | 638 7 | ] 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /images/numbered-bookmarks-list-from-all-files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/numbered-bookmarks-list-from-all-files.gif -------------------------------------------------------------------------------- /images/vscode-numbered-bookmarks-logo-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/vscode-numbered-bookmarks-logo-readme.png -------------------------------------------------------------------------------- /images/numbered-bookmarks-list-from-all-files-multi-root.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alefragnani/vscode-numbered-bookmarks/HEAD/images/numbered-bookmarks-list-from-all-files-multi-root.gif -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | url: https://github.com/alefragnani/vscode-numbered-bookmarks/discussions?discussions_q=category%3AQ%26A 5 | about: Ask a question about Numbered Bookmarks -------------------------------------------------------------------------------- /walkthrough/toggle.md: -------------------------------------------------------------------------------- 1 | ## Toggle Numbered Bookmarks 2 | 3 | You can easily Mark/Unmark Numbered Bookmarks on any position. 4 | 5 | ![Toggle](../images/numbered-bookmarks-toggle.png) 6 | 7 | > Tip: Use Keyboard Shortcut Ctrl + Shift + #number -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true 8 | } 9 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: alefragnani 2 | patreon: alefragnani 3 | custom: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=EP57F3B6FXKTU&lc=US&item_name=Alessandro%20Fragnani&item_number=vscode%20extensions¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted 4 | -------------------------------------------------------------------------------- /.vscode/numbered-bookmarks.json: -------------------------------------------------------------------------------- 1 | { 2 | "bookmarks": [ 3 | { 4 | "fsPath": "$ROOTPATH$\\package.json", 5 | "bookmarks": [ 6 | -1, 7 | 19, 8 | 13, 9 | -1, 10 | -1, 11 | -1, 12 | -1, 13 | -1, 14 | -1, 15 | -1 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /walkthrough/toggle.nls.pt-br.md: -------------------------------------------------------------------------------- 1 | ## Anternar Numbered Bookmarks 2 | 3 | Você pode adicionar/remover Numbered Bookmarks facilmente em qualquer posição. 4 | 5 | ![Alternar](../images/numbered-bookmarks-toggle.png) 6 | 7 | > Dica: Use o Atalho de Teclado Ctrl + Shift + #number -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Numbered Bookmarks 4 | title: "[FEATURE] - " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | typings/** 4 | **/*.ts 5 | **/*.map 6 | out/** 7 | node_modules/** 8 | .gitignore 9 | tsconfig.json 10 | test/** 11 | *.vsix 12 | package-lock.json 13 | webpack.config.js 14 | **/.github/ 15 | **/.git/** 16 | **/.git 17 | **/.gitmodules 18 | .devcontainer/ 19 | images/bookmark?-*.svg -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020", "DOM" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": ".", 11 | "alwaysStrict": true 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | ".vscode-test" 16 | ] 17 | } -------------------------------------------------------------------------------- /l10n/bundle.l10n.json: -------------------------------------------------------------------------------- 1 | { 2 | "No Bookmarks found": "No Bookmarks found", 3 | "Type a line number or a piece of code to navigate to": "Type a line number or a piece of code to navigate to", 4 | "The Bookmark {0} is not defined": "The Bookmark {0} is not defined", 5 | "Select a workspace": "Select a workspace", 6 | "Error loading Numbered Bookmarks: {0}": "Error loading Numbered Bookmarks: {0}" 7 | } -------------------------------------------------------------------------------- /l10n/bundle.l10n.pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "No Bookmarks found": "Nenhum Bookmark encontrado", 3 | "Type a line number or a piece of code to navigate to": "Digite um número de linha ou pedaço de código para navegar", 4 | "The Bookmark {0} is not defined": "O Bookmark {0} não está definido", 5 | "Select a workspace": "Selecione um workspace", 6 | "Error loading Numbered Bookmarks: {0}": "Erro lendo Numbered Bookmarks: {0}" 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help Numbered Bookmarks improve 4 | title: "[BUG] - " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Environment/version** 14 | 15 | - Extension version: 16 | - VSCode version: 17 | - OS version: 18 | 19 | **Steps to reproduce** 20 | 21 | 1. 22 | 2. 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint", 8 | "eamodio.tsl-problem-matcher", 9 | ], 10 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 11 | "unwantedRecommendations": [ 12 | 13 | ] 14 | } -------------------------------------------------------------------------------- /src/core/bookmark.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { QuickPickItem, Uri } from "vscode"; 7 | 8 | export interface Bookmark { 9 | line: number; 10 | column: number; 11 | } 12 | 13 | export interface BookmarkQuickPickItem extends QuickPickItem { 14 | uri: Uri; 15 | } 16 | -------------------------------------------------------------------------------- /walkthrough/workingWithRemotes.md: -------------------------------------------------------------------------------- 1 | ## Working with Remotes 2 | 3 | The extension fully supports [Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) scenarios. 4 | 5 | It means that when you connect to a _remote_ location, like a Docker Container, SSH or WSL, the extension will be available, ready to be used. 6 | 7 | > You don't need to install the extension on the remote. 8 | 9 | Better yet, if you use `numberedBookmarks.saveBookmarksInProject` setting defined as `true`, the bookmarks saved locally _will be available_ remotely, and you will be able to navigate and update the bookmarks. Just like it was a resource from folder you opened remotely. 10 | 11 | -------------------------------------------------------------------------------- /src/core/constants.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export const MAX_BOOKMARKS = 10; 7 | 8 | export const NO_BOOKMARK_DEFINED = { 9 | line: -1, 10 | column: 0 11 | } 12 | 13 | export const UNTITLED_SCHEME = "untitled"; 14 | 15 | export const DEFAULT_GUTTER_ICON_FILL_COLOR = "#00ff25"; 16 | export const DEFAULT_GUTTER_ICON_NUMBER_COLOR = "#000"; 17 | -------------------------------------------------------------------------------- /walkthrough/workingWithRemotes.nls.pt-br.md: -------------------------------------------------------------------------------- 1 | ## Trabalhando com Remotos 2 | 3 | A extensão suporta completamente cenários de [Desenvolvimento Remoto](https://code.visualstudio.com/docs/remote/remote-overview). 4 | 5 | Isso significa que quando você conecta a um local _remoto_, como Container Docker, SSH ou WSL, a extensão estará disponível, pronta para ser usada. 6 | 7 | > Você não precisa instalar a extensão no ambiente remoto. 8 | 9 | Melhor ainda, se você usar a configuração `numberedBookmarks.saveBookmarksInProject` definida como `true`, os bookmarks salvos localmente _estarão disponíveis_ remotamente, e você conseguirá navegar e atualizar os bookmarks. Assim como se fosse um recurso de uma pasta que você abriu remotamente. 10 | 11 | -------------------------------------------------------------------------------- /src/core/container.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { ExtensionContext } from "vscode"; 7 | 8 | export class Container { 9 | private static _extContext: ExtensionContext; 10 | 11 | public static get context(): ExtensionContext { 12 | return this._extContext; 13 | } 14 | 15 | public static set context(ec: ExtensionContext) { 16 | this._extContext = ec; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /walkthrough/customizingAppearance.md: -------------------------------------------------------------------------------- 1 | ## Customizing Appearance 2 | 3 | You can customize not only how the icon is show in the Gutter, but also add a background color to the bookmarked line and the overview ruller. 4 | 5 | Something like this in your settings: 6 | 7 | ```json 8 | "numberedBookmarks.gutterIconFillColor": "#00FF0077", 9 | // "numberedBookmarks.gutterIconNumberColor": "157EFB", 10 | "workbench.colorCustomizations": { 11 | ... 12 | "numberedBookmarks.lineBackground": "#0077ff2a", 13 | "numberedBookmarks.lineBorder": "#FF0000", 14 | "numberedBookmarks.overviewRuler": "#157EFB88" 15 | } 16 | ``` 17 | 18 | Could end up with a numbered bookmark like this: 19 | 20 | ![Customized Numbered Bookmark](customizedNumberedBookmark.png) -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error(err); 19 | console.error('Failed to run tests'); 20 | process.exit(1); 21 | } 22 | } 23 | 24 | main(); 25 | -------------------------------------------------------------------------------- /walkthrough/customizingAppearance.nls.pt-br.md: -------------------------------------------------------------------------------- 1 | ## Personalizando a Aparência 2 | 3 | Você pode personalizar não apenas como o ícone é apresentado na Medianiz, mas também adicionar cor de fundo a linha com bookmark e a régua de visão geral. 4 | 5 | Algo como isso nas suas configuráções: 6 | 7 | ```json 8 | "numberedBookmarks.gutterIconFillColor": "#00FF0077", 9 | // "numberedBookmarks.gutterIconNumberColor": "157EFB", 10 | "workbench.colorCustomizations": { 11 | ... 12 | "numberedBookmarks.lineBackground": "#0077ff2a", 13 | "numberedBookmarks.lineBorder": "#FF0000", 14 | "numberedBookmarks.overviewRuler": "#157EFB88" 15 | } 16 | ``` 17 | 18 | Pode deixar seus bookmarks assim: 19 | 20 | ![Numbered Bookmark Personalizado](customizedNumberedBookmark.png) -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "presentation": { 4 | "echo": false, 5 | "reveal": "always", 6 | "focus": false, 7 | "panel": "dedicated", 8 | "showReuseMessage": false 9 | }, 10 | "tasks": [ 11 | { 12 | "type": "npm", 13 | "script": "build", 14 | "group": "build", 15 | "problemMatcher": ["$ts-webpack", "$tslint-webpack"] 16 | }, 17 | { 18 | "type": "npm", 19 | "script": "watch", 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "isBackground": true, 25 | "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /images/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{number}} 6 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/core/file.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Uri } from "vscode"; 7 | import { Bookmark } from "./bookmark"; 8 | import { MAX_BOOKMARKS } from "./constants"; 9 | import { clearBookmarks } from "./operations"; 10 | 11 | export interface File { 12 | path: string; 13 | bookmarks: Bookmark[]; 14 | uri?: Uri; 15 | } 16 | 17 | export function createFile(filePath: string, uri?: Uri): File { 18 | const newFile: File = { 19 | path: filePath, 20 | bookmarks: [], 21 | uri 22 | } 23 | newFile.bookmarks.length = MAX_BOOKMARKS; 24 | clearBookmarks(newFile); 25 | return newFile; 26 | } 27 | -------------------------------------------------------------------------------- /walkthrough/navigateToNumberedBookmarks.md: -------------------------------------------------------------------------------- 1 | ## Navigate to Numbered Bookmarks 2 | 3 | Bookmarks represent positions in your code, so you can easily and quickly go back to them whenever necessary. 4 | 5 | The extension provides commands to quickly navigate every bookmark, from `Numbered Bookmarks: Jump to Bookmark 0` to `Numbered Bookmarks: Jump to Bookmark 9`, with a keyboard shortcut for each one of it (Ctrl + #number) 6 | 7 | But it is not limited to this. It also provides commands to see all Numbered Bookmarks within a file, or the entire workspace and easily go to it. Use the `Numbered Bookmarks: List` and `Numbered Bookmarks: List from All Files` command instead, and the extension will display a preview of the bookmarked line and it position. 8 | 9 | ![List](../images/numbered-bookmarks-list-from-all-files.gif) 10 | 11 | > Tip: If you simply navigate on the list, the editor will temporarily scroll to its position, giving you a better understanding if that bookmark is what you were looking for. 12 | 13 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | 7 | const timeout = async (ms = 200) => new Promise(resolve => setTimeout(resolve, ms)); 8 | 9 | suite('Extension Test Suite', () => { 10 | let extension: vscode.Extension; 11 | vscode.window.showInformationMessage('Start all tests.'); 12 | 13 | suiteSetup(() => { 14 | extension = vscode.extensions.getExtension('alefragnani.numbered-bookmarks') as vscode.Extension; 15 | }); 16 | 17 | test('Sample test', () => { 18 | assert.equal(-1, [1, 2, 3].indexOf(5)); 19 | assert.equal(-1, [1, 2, 3].indexOf(0)); 20 | }); 21 | 22 | test('Activation test', async () => { 23 | await extension.activate(); 24 | assert.equal(extension.isActive, true); 25 | }); 26 | 27 | test('Extension loads in VSCode and is active', async () => { 28 | await timeout(1500); 29 | assert.equal(extension.isActive, true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/utils/revealLocation.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { TextEditorRevealType, workspace } from "vscode"; 7 | 8 | export enum RevealLocation { 9 | Top = "top", 10 | Center = "center" 11 | } 12 | 13 | export function getRevealLocationConfig(ifOutsideViewport: boolean): TextEditorRevealType { 14 | const configuration = workspace.getConfiguration("numberedBookmarks"); 15 | const revealLocation = configuration.get("revealLocation", RevealLocation.Center); 16 | 17 | return revealLocation === RevealLocation.Top ? 18 | TextEditorRevealType.AtTop : 19 | ifOutsideViewport ? 20 | TextEditorRevealType.InCenterIfOutsideViewport : 21 | TextEditorRevealType.InCenter; 22 | } 23 | -------------------------------------------------------------------------------- /walkthrough/navigateToNumberedBookmarks.nls.pt-br.md: -------------------------------------------------------------------------------- 1 | ## Navegar para Numbered Bookmarks 2 | 3 | Bookmarks representam posições no seu código, então você pode voltar a elas de forma rápida e fácil sempre que necessário. 4 | 5 | Essa extensão disponibiliza comandos para nevegar facilmente para cada numbered bookmark, de `Numbered Bookmarks: Pular para Bookmark 0` até `Numbered Bookmarks: Pular para Bookmark 9`, com uma tecla de atalho para cada um (Ctrl + #number) 6 | 7 | Mas não está limitado a isso. Também disponibiliza comandos para visualizar todos os Numbered Bookmarks em um arquivo, ou em toda a área de trabalho. Use os comandos `Numbered Bookmarks: Listar` and `Numbered Bookmarks: Listar de Todos os Arquivos`, e a extensão apresentará uma prévia da linha com bookmark e sua posição. 8 | 9 | ![Lista](../images/numbered-bookmarks-list-from-all-files.gif) 10 | 11 | > Dica: Se você simplesmente navegar pela lista, o editor irá rolar temporariamente para a posição do bookmark, dando-lhe um entendimento melhor se o bookmark é aquele que você está procurando. 12 | 13 | -------------------------------------------------------------------------------- /src/whats-new/commands.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { commands } from "vscode"; 7 | import { Container } from "../core/container"; 8 | import { WhatsNewManager } from "../../vscode-whats-new/src/Manager"; 9 | import { NumberedBookmarksContentProvider, NumberedBookmarksSocialMediaProvider } from "./contentProvider"; 10 | 11 | export async function registerWhatsNew() { 12 | const provider = new NumberedBookmarksContentProvider(); 13 | const viewer = new WhatsNewManager(Container.context) 14 | .registerContentProvider("alefragnani", "numbered-bookmarks", provider) 15 | .registerSocialMediaProvider(new NumberedBookmarksSocialMediaProvider()); 16 | await viewer.showPageInActivation(); 17 | Container.context.subscriptions.push(commands.registerCommand("numberedBookmarks.whatsNew", () => viewer.showPage())); 18 | Container.context.subscriptions.push(commands.registerCommand("numberedBookmarks.whatsNewContextMenu", () => viewer.showPage())); 19 | } 20 | -------------------------------------------------------------------------------- /walkthrough/inspiredInDelphiButOpenToOthers.md: -------------------------------------------------------------------------------- 1 | ## Inpired in Delphi, but open to others 2 | 3 | The extension was inspired in Delphi, because I was a Delphi developer for a long time, and love it's bookmarks. But it supports different editors as well. 4 | 5 | To change a bit how the extension works (toggling and navigation), simply play with the `numberedBookmarks.navigateThroughAllFiles` setting: 6 | 7 | Value | Explanation 8 | --------- | --------- 9 | `false` | _default_ - same behavior as today 10 | `replace` | you can't have the same numbered bookmark in different files 11 | `allowDuplicates` | you can have the same numbered bookmark in different files, and if you jump repeatedly to the same number, it will look on other files. 12 | 13 | ### IntelliJ / UltraEdit developers 14 | 15 | If you are an **IntelliJ** or **UltraEdit** user, you will notice the numbered bookmarks works a bit different from the default behavior. 16 | 17 | To make **Numbered Bookmarks** works the same way as these other tools, simply add `"numberedBookmarks.navigateThroughAllFiles": replace"` to your setting and you are good to go. 18 | 19 | 20 | 21 | 24 | 25 |
22 | Open Settings 23 |
-------------------------------------------------------------------------------- /walkthrough/inspiredInDelphiButOpenToOthers.nls.pt-br.md: -------------------------------------------------------------------------------- 1 | ## Inspirado no Delphi, mas aberto a outros 2 | 3 | A extensão foi inspirada no Delphi, pois eu fui um desenvolvedor Delphi por muito tempo, e adoro seus bookmarks. Mas ele suporta diferentes editores também. 4 | 5 | Para mudar um pouco como a extensão funciona (alternar e navegar), basta brincar com a configuração `numberedBookmarks.navigateThroughAllFiles`: 6 | 7 | Valor | Explicação 8 | --------- | --------- 9 | `false` | _padrão_ - mesmo comportamento de hoje 10 | `replace` | você não pode ter o mesmo numbered bookmark em arquivos distintos 11 | `allowDuplicates` | você pode ter o mesmo numbered bookmark em arquivos distintos, e se você acionar repetidamente ao mesmo número, ele irá olhar para os outros arquivos. 12 | 13 | ### Desenvolvedores IntelliJ / UltraEdit 14 | 15 | Se você é um usuário do **IntelliJ** ou **UltraEdit**, você vai notar que o numbered bookmarks funciona um pouco diferente do seu comportamento padrão. 16 | 17 | Para fazer **Numbered Bookmarks** funcionar do mesmo jeito dessas ferramentas, simplesmente adicione `"numberedBookmarks.navigateThroughAllFiles": replace"` nas suas configurações e aproveite. 18 | 19 | 20 | 21 | 24 | 25 |
22 | Abrir Configurações 23 |
-------------------------------------------------------------------------------- /src/quickpick/controllerPicker.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import path = require("path"); 7 | import { l10n, QuickPickItem, window } from "vscode"; 8 | import { codicons } from "vscode-ext-codicons"; 9 | import { Controller } from "../core/controller"; 10 | 11 | interface ControllerQuickPickItem extends QuickPickItem { 12 | controller: Controller; 13 | } 14 | 15 | export async function pickController(controllers: Controller[], activeController: Controller): Promise { 16 | 17 | if (controllers.length === 1) { 18 | return activeController; 19 | } 20 | 21 | const items: ControllerQuickPickItem[] = controllers.map(controller => { 22 | return { 23 | label: codicons.root_folder + ' ' + controller.workspaceFolder.name, 24 | description: path.dirname(controller.workspaceFolder.uri.path), 25 | controller: controller 26 | } 27 | } 28 | ); 29 | 30 | const selection = await window.showQuickPick(items, { 31 | placeHolder: l10n.t('Select a workspace') 32 | }); 33 | 34 | if (typeof selection === "undefined") { 35 | return undefined 36 | } 37 | 38 | return selection.controller; 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 13 | // "skipFiles": ["/**", "**/node_modules/**", "**/app/out/vs/**", "**/extensions/**"], 14 | "smartStep": true, 15 | "sourceMaps": true 16 | }, 17 | { 18 | "name": "Launch Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--disable-extensions", 24 | "--extensionDevelopmentPath=${workspaceFolder}", 25 | "--extensionTestsPath=${workspaceFolder}/out/src/test/suite/index" 26 | ], 27 | "outFiles": [ 28 | "${workspaceFolder}/out/src/test/**/*.js" 29 | ], 30 | "preLaunchTask": "npm: test-compile" 31 | }, 32 | { 33 | "name": "Run Web Extension in VS Code", 34 | "type": "pwa-extensionHost", 35 | "debugWebWorkerHost": true, 36 | "request": "launch", 37 | "args": [ 38 | "--extensionDevelopmentPath=${workspaceFolder}", 39 | "--extensionDevelopmentKind=web" 40 | ], 41 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 42 | "preLaunchTask": "npm: watch" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | push: 7 | branches: [ master ] 8 | pull_request: 9 | branches: [ master ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | build: 15 | # The type of runner that the job will run on 16 | strategy: 17 | matrix: 18 | os: [macos-latest, ubuntu-latest, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | with: 27 | submodules: true 28 | 29 | # Runs a set of commands using the runners shell 30 | - name: Install Node.js 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: 20.x 34 | cache: 'npm' 35 | 36 | # Runs the tests 37 | - name: Cache VS Code downloads 38 | uses: actions/cache@v4 39 | with: 40 | path: .vscode-test 41 | key: vscode-test-${{ matrix.os }}-${{ hashFiles('**/package.json') }} 42 | restore-keys: | 43 | vscode-test-${{ matrix.os }}- 44 | - run: npm ci 45 | - run: xvfb-run -a npm test 46 | if: runner.os == 'Linux' 47 | - run: npm test 48 | if: runner.os != 'Linux' 49 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the GPLv3 License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | //@ts-check 8 | 9 | 'use strict'; 10 | 11 | const path = require('path'); 12 | const TerserPlugin = require('terser-webpack-plugin'); 13 | const webpack = require('webpack'); 14 | 15 | 16 | 17 | /**@type {import('webpack').Configuration}*/ 18 | const config = { 19 | entry: "./src/extension.ts", 20 | optimization: { 21 | minimizer: [new TerserPlugin({ 22 | parallel: true, 23 | terserOptions: { 24 | ecma: 2019, 25 | keep_classnames: false, 26 | mangle: true, 27 | module: true 28 | } 29 | })], 30 | }, 31 | 32 | devtool: 'source-map', 33 | externals: { 34 | vscode: "commonjs vscode" // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 35 | }, 36 | resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 37 | extensions: ['.ts', '.js'] 38 | }, 39 | module: { 40 | rules: [{ 41 | test: /\.ts$/, 42 | exclude: /node_modules/, 43 | use: [{ 44 | loader: 'ts-loader', 45 | }] 46 | }] 47 | }, 48 | }; 49 | 50 | const nodeConfig = { 51 | ...config, 52 | target: "node", 53 | output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 54 | path: path.resolve(__dirname, 'dist'), 55 | filename: 'extension-node.js', 56 | libraryTarget: "commonjs2", 57 | devtoolModuleFilenameTemplate: "../[resource-path]", 58 | }, 59 | } 60 | 61 | module.exports = [nodeConfig]; 62 | -------------------------------------------------------------------------------- /src/utils/reveal.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Selection, Tab, TabInputText, TextEditorRevealType, Uri, ViewColumn, window, workspace } from "vscode"; 7 | import { Bookmark } from "../core/bookmark"; 8 | import { getRevealLocationConfig } from "./revealLocation"; 9 | 10 | export function revealLine(line: number, directJump?: boolean) { 11 | const newSe = new Selection(line, 0, line, 0); 12 | window.activeTextEditor.selection = newSe; 13 | window.activeTextEditor.revealRange(newSe, getRevealLocationConfig(directJump)); 14 | } 15 | 16 | export function revealPosition(line: number, column: number): void { 17 | if (isNaN(column)) { 18 | revealLine(line); 19 | } else { 20 | const revealType: TextEditorRevealType = getRevealLocationConfig(line === window.activeTextEditor.selection.active.line); 21 | const newPosition = new Selection(line, column, line, column); 22 | window.activeTextEditor.selection = newPosition; 23 | window.activeTextEditor.revealRange(newPosition, revealType); 24 | } 25 | } 26 | 27 | export async function previewPositionInDocument(point: Bookmark, uri: Uri): Promise { 28 | const textDocument = await workspace.openTextDocument(uri); 29 | await window.showTextDocument(textDocument, { preserveFocus: true, preview: true } ); 30 | revealPosition(point.line - 1, point.column - 1); 31 | } 32 | 33 | export async function revealPositionInDocument(point: Bookmark, uri: Uri): Promise { 34 | const tabGroupColumn = findTabGroupColumn(uri, window.activeTextEditor.viewColumn); 35 | 36 | const textDocument = await workspace.openTextDocument(uri); 37 | await window.showTextDocument(textDocument, tabGroupColumn, false); 38 | revealPosition(point.line, point.column); 39 | } 40 | 41 | function findTabGroupColumn(uri: Uri, column: ViewColumn): ViewColumn { 42 | if (window.tabGroups.all.length === 1) { 43 | return column; 44 | } 45 | 46 | for (const tab of window.tabGroups.activeTabGroup.tabs) { 47 | if (isTabOfUri(tab, uri)) { 48 | return tab.group.viewColumn; 49 | } 50 | } 51 | 52 | for (const tabGroup of window.tabGroups.all) { 53 | if (tabGroup.viewColumn === column) 54 | continue; 55 | 56 | for (const tab of tabGroup.tabs) { 57 | if (isTabOfUri(tab, uri)) { 58 | return tab.group.viewColumn; 59 | } 60 | } 61 | } 62 | 63 | return column; 64 | } 65 | 66 | function isTabOfUri(tab: Tab, uri: Uri): boolean { 67 | return tab.input instanceof TabInputText && 68 | tab.input.uri.fsPath.toLocaleLowerCase() === uri.fsPath.toLocaleLowerCase() 69 | } 70 | -------------------------------------------------------------------------------- /src/storage/workspaceState.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { workspace, window, WorkspaceFolder, l10n } from "vscode"; 7 | import { Container } from "../core/container"; 8 | import { Controller } from "../core/controller"; 9 | import { appendPath, createDirectoryUri, readFileUri, uriExists, writeFileUri } from "../utils/fs"; 10 | 11 | export function canSaveBookmarksInProject(): boolean { 12 | let saveBookmarksInProject: boolean = workspace.getConfiguration("numberedBookmarks").get("saveBookmarksInProject", false); 13 | 14 | // really use saveBookmarksInProject 15 | // 0. has at least a folder opened 16 | // 1. is a valid workspace/folder 17 | // 2. has only one workspaceFolder 18 | // let hasBookmarksFile: boolean = false; 19 | if (saveBookmarksInProject && !workspace.workspaceFolders) { 20 | saveBookmarksInProject = false; 21 | } 22 | 23 | return saveBookmarksInProject; 24 | } 25 | 26 | export async function loadBookmarks(workspaceFolder: WorkspaceFolder): Promise { 27 | const saveBookmarksInProject: boolean = canSaveBookmarksInProject(); 28 | 29 | const newController = new Controller(workspaceFolder); 30 | 31 | if (saveBookmarksInProject) { 32 | const bookmarksFileInProject = appendPath(appendPath(workspaceFolder.uri, ".vscode"), "numbered-bookmarks.json"); 33 | if (! await uriExists(bookmarksFileInProject)) { 34 | return newController; 35 | } 36 | 37 | try { 38 | const contents = await readFileUri(bookmarksFileInProject); 39 | newController.loadFrom(contents, true); 40 | return newController; 41 | } catch (error) { 42 | window.showErrorMessage(l10n.t("Error loading Numbered Bookmarks: {0}", error.toString())); 43 | return newController; 44 | } 45 | } else { 46 | const savedBookmarks = Container.context.workspaceState.get("numberedBookmarks", ""); 47 | if (savedBookmarks !== "") { 48 | newController.loadFrom(JSON.parse(savedBookmarks)); 49 | } 50 | return newController; 51 | } 52 | } 53 | 54 | export function saveBookmarks(controller: Controller): void { 55 | const saveBookmarksInProject: boolean = canSaveBookmarksInProject(); 56 | 57 | if (saveBookmarksInProject) { 58 | const bookmarksFileInProject = appendPath(appendPath(controller.workspaceFolder.uri, ".vscode"), "numbered-bookmarks.json"); 59 | if (!uriExists(appendPath(controller.workspaceFolder.uri, ".vscode"))) { 60 | createDirectoryUri(appendPath(controller.workspaceFolder.uri, ".vscode")); 61 | } 62 | writeFileUri(bookmarksFileInProject, JSON.stringify(controller.zip(), null, "\t")); 63 | } else { 64 | Container.context.workspaceState.update("numberedBookmarks", JSON.stringify(controller.zip())); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /package.nls.pt-br.json: -------------------------------------------------------------------------------- 1 | { 2 | "numberedBookmarks.commands.toggleBookmark0.title": "Alternar Bookmark 0", 3 | "numberedBookmarks.commands.toggleBookmark1.title": "Alternar Bookmark 1", 4 | "numberedBookmarks.commands.toggleBookmark2.title": "Alternar Bookmark 2", 5 | "numberedBookmarks.commands.toggleBookmark3.title": "Alternar Bookmark 3", 6 | "numberedBookmarks.commands.toggleBookmark4.title": "Alternar Bookmark 4", 7 | "numberedBookmarks.commands.toggleBookmark5.title": "Alternar Bookmark 5", 8 | "numberedBookmarks.commands.toggleBookmark6.title": "Alternar Bookmark 6", 9 | "numberedBookmarks.commands.toggleBookmark7.title": "Alternar Bookmark 7", 10 | "numberedBookmarks.commands.toggleBookmark8.title": "Alternar Bookmark 8", 11 | "numberedBookmarks.commands.toggleBookmark9.title": "Alternar Bookmark 9", 12 | "numberedBookmarks.commands.jumpToBookmark0.title": "Pular para Bookmark 0", 13 | "numberedBookmarks.commands.jumpToBookmark1.title": "Pular para Bookmark 1", 14 | "numberedBookmarks.commands.jumpToBookmark2.title": "Pular para Bookmark 2", 15 | "numberedBookmarks.commands.jumpToBookmark3.title": "Pular para Bookmark 3", 16 | "numberedBookmarks.commands.jumpToBookmark4.title": "Pular para Bookmark 4", 17 | "numberedBookmarks.commands.jumpToBookmark5.title": "Pular para Bookmark 5", 18 | "numberedBookmarks.commands.jumpToBookmark6.title": "Pular para Bookmark 6", 19 | "numberedBookmarks.commands.jumpToBookmark7.title": "Pular para Bookmark 7", 20 | "numberedBookmarks.commands.jumpToBookmark8.title": "Pular para Bookmark 8", 21 | "numberedBookmarks.commands.jumpToBookmark9.title": "Pular para Bookmark 9", 22 | "numberedBookmarks.commands.list.title": "Listar", 23 | "numberedBookmarks.commands.listFromAllFiles.title": "Listar de Todos os Arquivos", 24 | "numberedBookmarks.commands.clear.title": "Limpar", 25 | "numberedBookmarks.commands.clearFromAllFiles.title": "Limpar de Todos os Arquivos", 26 | "numberedBookmarks.commands.whatsNew.title": "Novidades", 27 | "numberedBookmarks.commands.whatsNewContextMenu.title": "Novidades", 28 | "numberedBookmarks.context.toggle.label": "Numbered Bookmarks: Alternar", 29 | "numberedBookmarks.context.jump.label": "Numbered Bookmarks: Pular", 30 | "numberedBookmarks.configuration.title": "Numbered Bookmarks", 31 | "numberedBookmarks.configuration.saveBookmarksInProject.description": "Permite que os Bookmarks sejam salvos (e restaurados) localmente no Projeto/Pasta aberto ao invés do VS Code", 32 | "numberedBookmarks.configuration.experimental.enableNewStickyEngine.description": "Experimental. Ativa o novo motor Sticky engine com suporte a Formatadores, detecção de mudança de fonte e operações de desfazer melhoradas", 33 | "numberedBookmarks.configuration.keepBookmarksOnLineDelete.description": "Determina se bookmarks em linhas excluídas devem ser mantidos no arquivo, movendo-os para a próxima linha, ao invés de excluídos com a linha onde se encontravam", 34 | "numberedBookmarks.configuration.showBookmarkNotDefinedWarning.description": "Define se uma notificação deve ser apresentada quando o Bookmark não está definido.", 35 | "numberedBookmarks.configuration.navigateThroughAllFiles.description": "Permite que a navegação apresente Bookmarks em todos os arquivos do projeto, ao invés de apenas no arquivo corrente", 36 | "numberedBookmarks.configuration.gutterIconFillColor.description": "Cor de fundo do ícone de Bookmark", 37 | "numberedBookmarks.configuration.gutterIconNumberColor.description": "Cor do número no ícone de Bookmark", 38 | "numberedBookmarks.configuration.revealLocation.description": "Especifica o local onde a linha com bookmark será exibida", 39 | "numberedBookmarks.configuration.revealLocation.enumDescriptions.top": "Exibe a linha com bookmark no topo do editor", 40 | "numberedBookmarks.configuration.revealLocation.enumDescriptions.center": "Exibe a linha com bookmark no centro do editor", 41 | "numberedBookmarks.colors.lineBackground.description": "Cor de fundo para linha com Bookmark", 42 | "numberedBookmarks.colors.lineBorder.description": "Cor de fundo da borda ao redor da linha com Bookmark", 43 | "numberedBookmarks.colors.overviewRuler.description": "Cor do marcador de régua com Bookmarks" 44 | } -------------------------------------------------------------------------------- /src/core/operations.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Uri, workspace, WorkspaceFolder } from "vscode"; 7 | import { Bookmark, BookmarkQuickPickItem } from "./bookmark"; 8 | import { MAX_BOOKMARKS, NO_BOOKMARK_DEFINED } from "./constants"; 9 | import { uriExists } from "../utils/fs"; 10 | import { File } from "./file"; 11 | 12 | export function isBookmarkDefined(bookmark: Bookmark): boolean { 13 | return bookmark.line !== NO_BOOKMARK_DEFINED.line; 14 | } 15 | 16 | export function hasBookmarks(file: File): boolean { 17 | let hasAny = false; 18 | for (const element of file.bookmarks) { 19 | hasAny = isBookmarkDefined(element); 20 | if (hasAny) { 21 | break; 22 | } 23 | } 24 | return hasAny; 25 | } 26 | 27 | export function listBookmarks(file: File, workspaceFolder: WorkspaceFolder) { 28 | 29 | // eslint-disable-next-line no-async-promise-executor 30 | return new Promise(async (resolve, reject) => { 31 | 32 | if (!hasBookmarks(file)) { 33 | resolve({}); 34 | return; 35 | } 36 | 37 | let uriDocBookmark: Uri; 38 | if (file.uri) { 39 | uriDocBookmark = file.uri; 40 | } else { 41 | if (!workspaceFolder) { 42 | uriDocBookmark = Uri.file(file.path); 43 | } else { 44 | const prefix = workspaceFolder.uri.path.endsWith("/") 45 | ? workspaceFolder.uri.path 46 | : `${workspaceFolder.uri.path}/`; 47 | uriDocBookmark = workspaceFolder.uri.with({ 48 | path: `${prefix}/${file.path}` 49 | }); 50 | } 51 | } 52 | 53 | if (! await uriExists(uriDocBookmark)) { 54 | resolve({}); 55 | return; 56 | } 57 | 58 | workspace.openTextDocument(uriDocBookmark).then(doc => { 59 | 60 | const items: BookmarkQuickPickItem[] = []; 61 | const invalids = []; 62 | for (const element of file.bookmarks) { 63 | // fix for modified files 64 | if (isBookmarkDefined(element)) { 65 | if (element.line <= doc.lineCount) { 66 | const bookmarkLine = element.line + 1; 67 | const bookmarkColumn = element.column + 1; 68 | const lineText = doc.lineAt(bookmarkLine - 1).text.trim(); 69 | items.push({ 70 | label: lineText, 71 | description: "(Ln " + bookmarkLine.toString() + ", Col " + 72 | bookmarkColumn.toString() + ")", 73 | detail: file.path, 74 | uri: uriDocBookmark 75 | }); 76 | } else { 77 | invalids.push(element); 78 | } 79 | } 80 | } 81 | 82 | if (invalids.length > 0) { 83 | // tslint:disable-next-line:prefer-for-of 84 | for (let indexI = 0; indexI < invalids.length; indexI++) { 85 | file.bookmarks[ invalids[ indexI ] ] = NO_BOOKMARK_DEFINED; 86 | } 87 | } 88 | 89 | resolve(items); 90 | return; 91 | }); 92 | }); 93 | } 94 | 95 | export function clearBookmarks(file: File) { 96 | for (let index = 0; index < MAX_BOOKMARKS; index++) { 97 | file.bookmarks[ index ] = NO_BOOKMARK_DEFINED; 98 | } 99 | } 100 | 101 | export function indexOfBookmark(file: File, line: number): number { 102 | for (let index = 0; index < file.bookmarks.length; index++) { 103 | const bookmark = file.bookmarks[index]; 104 | if (bookmark.line === line) { 105 | return index; 106 | } 107 | } 108 | return -1; 109 | } 110 | -------------------------------------------------------------------------------- /src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import os = require("os"); 7 | import path = require("path"); 8 | import { Uri, workspace, WorkspaceFolder } from "vscode"; 9 | import { Bookmark } from "../core/bookmark"; 10 | import { UNTITLED_SCHEME } from "../core/constants"; 11 | import { File } from "../core/file"; 12 | 13 | export function getRelativePath(folder: string, filePath: string) { 14 | if (!folder) { 15 | return filePath; 16 | } 17 | 18 | let relativePath = path.relative(folder, filePath); 19 | 20 | // multiplatform 21 | if (os.platform() === "win32") { 22 | relativePath = relativePath.replace(/\\/g, "/"); 23 | } 24 | 25 | return relativePath; 26 | } 27 | 28 | export function appendPath(uri: Uri, pathSuffix: string): Uri { 29 | const pathPrefix = uri.path.endsWith("/") ? uri.path : `${uri.path}/`; 30 | const filePath = `${pathPrefix}${pathSuffix}`; 31 | 32 | return uri.with({ 33 | path: filePath 34 | }); 35 | } 36 | 37 | export function uriJoin(uri: Uri, ...paths: string[]): string { 38 | return path.join(uri.fsPath, ...paths); 39 | } 40 | 41 | export function uriWith(uri: Uri, prefix: string, filePath: string): Uri { 42 | const newPrefix = prefix === "/" 43 | ? "" 44 | : prefix; 45 | 46 | return uri.with({ 47 | path: `${newPrefix}/${filePath}` 48 | }); 49 | } 50 | 51 | export async function uriExists(uri: Uri): Promise { 52 | 53 | if (uri.scheme === UNTITLED_SCHEME) { 54 | return true; 55 | } 56 | 57 | try { 58 | await workspace.fs.stat(uri); 59 | return true; 60 | } catch { 61 | return false; 62 | } 63 | } 64 | 65 | export async function uriDelete(uri: Uri): Promise { 66 | 67 | if (uri.scheme === UNTITLED_SCHEME) { 68 | return true; 69 | } 70 | 71 | try { 72 | await workspace.fs.delete(uri); 73 | return true; 74 | } catch { 75 | return false; 76 | } 77 | } 78 | 79 | export async function fileExists(filePath: string): Promise { 80 | try { 81 | await workspace.fs.stat(Uri.parse(filePath)); 82 | return true; 83 | } catch { 84 | return false; 85 | } 86 | } 87 | 88 | export async function createDirectoryUri(uri: Uri): Promise { 89 | return workspace.fs.createDirectory(uri); 90 | } 91 | 92 | export async function createDirectory(dir: string): Promise { 93 | return workspace.fs.createDirectory(Uri.parse(dir)); 94 | } 95 | 96 | export async function readFile(filePath: string): Promise { 97 | const bytes = await workspace.fs.readFile(Uri.parse(filePath)); 98 | return JSON.parse(new TextDecoder('utf-8').decode(bytes)); 99 | } 100 | 101 | export async function readFileUri(uri: Uri): Promise { 102 | const bytes = await workspace.fs.readFile(uri); 103 | return JSON.parse(new TextDecoder('utf-8').decode(bytes)); 104 | } 105 | 106 | export async function readRAWFileUri(uri: Uri): Promise { 107 | const bytes = await workspace.fs.readFile(uri); 108 | return new TextDecoder('utf-8').decode(bytes); 109 | } 110 | 111 | export async function writeFile(filePath: string, contents: string): Promise { 112 | const writeData = new TextEncoder().encode(contents); 113 | await workspace.fs.writeFile(Uri.parse(filePath), writeData); 114 | } 115 | 116 | export async function writeFileUri(uri: Uri, contents: string): Promise { 117 | const writeData = new TextEncoder().encode(contents); 118 | await workspace.fs.writeFile(uri, writeData); 119 | } 120 | 121 | export function parsePosition(position: string): Bookmark | undefined { 122 | const re = new RegExp(/\(Ln\s(\d+),\sCol\s(\d+)\)/); 123 | const matches = re.exec(position); 124 | if (matches) { 125 | return { 126 | line: parseInt(matches[ 1 ], 10), 127 | column: parseInt(matches[ 2 ], 10) 128 | }; 129 | } 130 | return undefined; 131 | } 132 | 133 | export function getFileUri(file: File, workspaceFolder: WorkspaceFolder): Uri { 134 | if (file.uri) { 135 | return file.uri; 136 | } 137 | 138 | if (!workspaceFolder) { 139 | return Uri.file(file.path); 140 | } 141 | 142 | const prefix = workspaceFolder.uri.path.endsWith("/") 143 | ? workspaceFolder.uri.path 144 | : `${workspaceFolder.uri.path}/`; 145 | return uriWith(workspaceFolder.uri, prefix, file.path); 146 | } 147 | -------------------------------------------------------------------------------- /src/decoration/decoration.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { DecorationRenderOptions, OverviewRulerLane, Range, TextEditor, TextEditorDecorationType, ThemeColor, Uri, workspace, window } from "vscode"; 7 | import { createLineDecoration } from "vscode-ext-decoration"; 8 | import { DEFAULT_GUTTER_ICON_NUMBER_COLOR, DEFAULT_GUTTER_ICON_FILL_COLOR, MAX_BOOKMARKS, NO_BOOKMARK_DEFINED } from "../core/constants"; 9 | import { File } from "../core/file"; 10 | import { clearBookmarks } from "../core/operations"; 11 | 12 | function createGutterRulerDecoration( 13 | overviewRulerLane?: OverviewRulerLane, 14 | overviewRulerColor?: string | ThemeColor, 15 | gutterIconPath?: string | Uri): TextEditorDecorationType { 16 | 17 | const decorationOptions: DecorationRenderOptions = { 18 | gutterIconPath, 19 | overviewRulerLane, 20 | overviewRulerColor 21 | }; 22 | 23 | decorationOptions.isWholeLine = false; 24 | 25 | return window.createTextEditorDecorationType(decorationOptions); 26 | } 27 | 28 | export interface TextEditorDecorationTypePair { 29 | gutterDecoration: TextEditorDecorationType; 30 | lineDecoration: TextEditorDecorationType; 31 | } 32 | 33 | export function createBookmarkDecorations(): TextEditorDecorationTypePair[] { 34 | const decorators: TextEditorDecorationTypePair[] = []; 35 | for (let number = 0; number <= 9; number++) { 36 | const iconFillColor = workspace.getConfiguration("numberedBookmarks").get("gutterIconFillColor", DEFAULT_GUTTER_ICON_FILL_COLOR); 37 | const iconNumberColor = workspace.getConfiguration("numberedBookmarks").get("gutterIconNumberColor", DEFAULT_GUTTER_ICON_NUMBER_COLOR); 38 | const iconPath = Uri.parse( 39 | `data:image/svg+xml,${encodeURIComponent( 40 | ` ${number} `, 41 | )}`, 42 | ); 43 | 44 | const overviewRulerColor = new ThemeColor('numberedBookmarks.overviewRuler'); 45 | const lineBackground = new ThemeColor('numberedBookmarks.lineBackground'); 46 | const lineBorder = new ThemeColor('numberedBookmarks.lineBorder'); 47 | 48 | const gutterDecoration = createGutterRulerDecoration(OverviewRulerLane.Full, overviewRulerColor, iconPath); 49 | const lineDecoration = createLineDecoration(lineBackground, lineBorder); 50 | decorators.push( { gutterDecoration, lineDecoration }); 51 | } 52 | return decorators; 53 | } 54 | 55 | export function updateDecorationsInActiveEditor(activeEditor: TextEditor, activeBookmark: File, 56 | getDecorationPair: (n: number) => TextEditorDecorationTypePair) { 57 | 58 | if (!activeEditor) { 59 | return; 60 | } 61 | 62 | if (!activeBookmark) { 63 | return; 64 | } 65 | 66 | let books: Range[] = []; 67 | // Remove all bookmarks if active file is empty 68 | if (activeEditor.document.lineCount === 1 && activeEditor.document.lineAt(0).text === "") { 69 | clearBookmarks(activeBookmark); 70 | } else { 71 | const invalids = []; 72 | for (let index = 0; index < MAX_BOOKMARKS; index++) { 73 | books = []; 74 | if (activeBookmark.bookmarks[ index ].line < 0) { 75 | const decors = getDecorationPair(index); 76 | activeEditor.setDecorations(decors.gutterDecoration, books); 77 | activeEditor.setDecorations(decors.lineDecoration, books); 78 | } else { 79 | const element = activeBookmark.bookmarks[ index ]; 80 | if (element.line < activeEditor.document.lineCount) { 81 | const decoration = new Range(element.line, 0, element.line, 0); 82 | books.push(decoration); 83 | const decors = getDecorationPair(index); 84 | activeEditor.setDecorations(decors.gutterDecoration, books); 85 | activeEditor.setDecorations(decors.lineDecoration, books); 86 | } else { 87 | invalids.push(index); 88 | } 89 | } 90 | } 91 | 92 | if (invalids.length > 0) { 93 | // tslint:disable-next-line:prefer-for-of 94 | for (let indexI = 0; indexI < invalids.length; indexI++) { 95 | activeBookmark.bookmarks[ invalids[ indexI ] ] = NO_BOOKMARK_DEFINED; 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "numberedBookmarks.commands.toggleBookmark0.title": "Toggle Bookmark 0", 3 | "numberedBookmarks.commands.toggleBookmark1.title": "Toggle Bookmark 1", 4 | "numberedBookmarks.commands.toggleBookmark2.title": "Toggle Bookmark 2", 5 | "numberedBookmarks.commands.toggleBookmark3.title": "Toggle Bookmark 3", 6 | "numberedBookmarks.commands.toggleBookmark4.title": "Toggle Bookmark 4", 7 | "numberedBookmarks.commands.toggleBookmark5.title": "Toggle Bookmark 5", 8 | "numberedBookmarks.commands.toggleBookmark6.title": "Toggle Bookmark 6", 9 | "numberedBookmarks.commands.toggleBookmark7.title": "Toggle Bookmark 7", 10 | "numberedBookmarks.commands.toggleBookmark8.title": "Toggle Bookmark 8", 11 | "numberedBookmarks.commands.toggleBookmark9.title": "Toggle Bookmark 9", 12 | "numberedBookmarks.commands.jumpToBookmark0.title": "Jump to Bookmark 0", 13 | "numberedBookmarks.commands.jumpToBookmark1.title": "Jump to Bookmark 1", 14 | "numberedBookmarks.commands.jumpToBookmark2.title": "Jump to Bookmark 2", 15 | "numberedBookmarks.commands.jumpToBookmark3.title": "Jump to Bookmark 3", 16 | "numberedBookmarks.commands.jumpToBookmark4.title": "Jump to Bookmark 4", 17 | "numberedBookmarks.commands.jumpToBookmark5.title": "Jump to Bookmark 5", 18 | "numberedBookmarks.commands.jumpToBookmark6.title": "Jump to Bookmark 6", 19 | "numberedBookmarks.commands.jumpToBookmark7.title": "Jump to Bookmark 7", 20 | "numberedBookmarks.commands.jumpToBookmark8.title": "Jump to Bookmark 8", 21 | "numberedBookmarks.commands.jumpToBookmark9.title": "Jump to Bookmark 9", 22 | "numberedBookmarks.commands.list.title": "List", 23 | "numberedBookmarks.commands.listFromAllFiles.title": "List from All Files", 24 | "numberedBookmarks.commands.clear.title": "Clear", 25 | "numberedBookmarks.commands.clearFromAllFiles.title": "Clear from All Files", 26 | "numberedBookmarks.commands.whatsNew.title": "What's New", 27 | "numberedBookmarks.commands.whatsNewContextMenu.title": "What's New", 28 | "numberedBookmarks.context.toggle.label": "Numbered Bookmarks: Toggle", 29 | "numberedBookmarks.context.jump.label": "Numbered Bookmarks: Jump", 30 | "numberedBookmarks.configuration.title": "Numbered Bookmarks", 31 | "numberedBookmarks.configuration.saveBookmarksInProject.description": "Allow bookmarks to be saved (and restored) locally in the opened Project/Folder instead of VS Code", 32 | "numberedBookmarks.configuration.experimental.enableNewStickyEngine.description": "Experimental. Enables the new Sticky engine with support for Formatters, improved source change detections and undo operations", 33 | "numberedBookmarks.configuration.keepBookmarksOnLineDelete.description": "Specifies whether bookmarks on deleted line should be kept on file, moving it down to the next line, instead of deleting it with the line where it was toggled.", 34 | "numberedBookmarks.configuration.showBookmarkNotDefinedWarning.description": "Controls whether to show a warning when a bookmark is not defined", 35 | "numberedBookmarks.configuration.navigateThroughAllFiles.description": "Allow navigation look for bookmarks in all files in the project, instead of only the current", 36 | "numberedBookmarks.configuration.gutterIconFillColor.description": "Specify the color to use on gutter icon (fill color)", 37 | "numberedBookmarks.configuration.gutterIconNumberColor.description": "Specify the color to use on gutter icon (number color)", 38 | "numberedBookmarks.configuration.revealLocation.description": "Specifies the location where the bookmarked line will be revealed", 39 | "numberedBookmarks.configuration.revealLocation.enumDescriptions.top": "Reveals the bookmarked line at the top of the editor", 40 | "numberedBookmarks.configuration.revealLocation.enumDescriptions.center": "Reveals the bookmarked line in the center of the editor", 41 | "numberedBookmarks.colors.lineBackground.description": "Background color for the bookmarked line", 42 | "numberedBookmarks.colors.lineBorder.description": "Background color for the border around the bookmarked line", 43 | "numberedBookmarks.colors.overviewRuler.description": "Overview ruler marker color for bookmarks", 44 | "numberedBookmarks.walkthroughs.title": "Get Started with Numbered Bookmarks", 45 | "numberedBookmarks.walkthroughs.description": "Learn more about Numbered Bookmarks to optimize your workflow", 46 | "numberedBookmarks.walkthroughs.toggle.title": "Toggle Numbered Bookmarks", 47 | "numberedBookmarks.walkthroughs.toggle.description": "Easily Mark/Unmark Numbered Bookmarks at any position.\nAn icon is added to both the gutter and overview ruler to easily identify the lines with Numbered Bookmarks.", 48 | "numberedBookmarks.walkthroughs.navigateToNumberedBookmarks.title": "Navigate to Numbered Bookmarks", 49 | "numberedBookmarks.walkthroughs.navigateToNumberedBookmarks.description": "Quickly jump between bookmarked lines.\nSearch numbered bookmarks using the line's content and/or position.", 50 | "numberedBookmarks.walkthroughs.inspiredInDelphiButOpenToOthers.title": "Inspired in Delphi, but open to others", 51 | "numberedBookmarks.walkthroughs.inspiredInDelphiButOpenToOthers.description": "The extension was inspired in Delphi, because I was a Delphi developer for a long time, and love it's bookmarks, but it supports different editors as well.", 52 | "numberedBookmarks.walkthroughs.workingWithRemotes.title": "Working with Remotes", 53 | "numberedBookmarks.walkthroughs.workingWithRemotes.description": "The extension support [Remote Development](https://code.visualstudio.com/docs/remote/remote-overview) scenarios. Even installed locally, you can use Numbered Bookmarks in WSL, Containers, SSH and Codespaces.", 54 | "numberedBookmarks.walkthroughs.customizingAppearance.title": "Customizing Appearance", 55 | "numberedBookmarks.walkthroughs.customizingAppearance.description": "Customize how Numbered Bookmarks are presented, its icon, line and overview ruler\n[Open Settings - Gutter Icon](command:workbench.action.openSettings?%5B%22numberedBookmarks.gutterIcon%22%5D)\n[Open Settings - Line](command:workbench.action.openSettingsJson?%5B%22workbench.colorCustomizations%22%5D)" 56 | 57 | } -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot Instructions for Numbered Bookmarks 2 | 3 | ## Project Overview 4 | 5 | Numbered Bookmarks is a Visual Studio Code extension that helps users navigate code by marking and jumping to important positions with numbered bookmarks (0-9), inspired by Delphi IDE. 6 | 7 | ## Technology Stack 8 | 9 | - **Language**: TypeScript 10 | - **Target Platform**: VS Code Extension (Node.js) 11 | - **Build Tool**: Webpack 5 12 | - **Testing Framework**: Mocha with @vscode/test-electron 13 | - **Minimum VS Code Version**: 1.73.0 14 | - **License**: GPL-3.0 15 | 16 | ## Project Structure 17 | 18 | ``` 19 | src/ 20 | ├── core/ # Core bookmark logic (Controller, File, Bookmark, operations) 21 | ├── decoration/ # Gutter and line decorations 22 | ├── sticky/ # Sticky bookmark engine (legacy and new) 23 | ├── storage/ # Workspace state persistence 24 | ├── quickpick/ # Quick pick UI components 25 | ├── utils/ # Utility functions (fs, reveal, revealLocation) 26 | ├── whats-new/ # What's New feature 27 | └── extension.ts # Main extension entry point 28 | ``` 29 | 30 | ## Build and Test Commands 31 | 32 | ### Build 33 | - `npm run build` - Build for development with webpack 34 | - `npm run watch` - Watch mode for development 35 | - `npm run compile` - TypeScript compilation only 36 | - `npm run webpack` - Webpack build (development mode) 37 | - `npm run vscode:prepublish` - Production build (runs before publishing) 38 | 39 | ### Test 40 | - `npm test` - Run full test suite (compile, lint, and test) 41 | - `npm run just-test` - Run tests only (no compile or lint) 42 | - `npm run pretest` - Compile and lint before testing 43 | - `npm run lint` - Run ESLint on TypeScript files 44 | 45 | ### Development 46 | - `npm run webpack-dev` - Watch mode with webpack 47 | 48 | ## Code Style and Conventions 49 | 50 | ### General 51 | - Use 4 spaces for indentation (not tabs) 52 | - Use double quotes for strings 53 | - Include copyright header in all source files: 54 | ```typescript 55 | /*--------------------------------------------------------------------------------------------- 56 | * Copyright (c) Alessandro Fragnani. All rights reserved. 57 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 58 | *--------------------------------------------------------------------------------------------*/ 59 | ``` 60 | 61 | ### TypeScript 62 | - Target: ES2020 63 | - Use strict mode (`alwaysStrict: true`) 64 | - Prefer explicit types over `any` 65 | - Use modern ES6+ features (arrow functions, const/let, template literals) 66 | - Import from 'vscode' module for VS Code API 67 | - Use named imports where possible 68 | 69 | ### Naming Conventions 70 | - Classes: PascalCase (e.g., `Controller`, `BookmarkQuickPickItem`) 71 | - Functions/Methods: camelCase (e.g., `loadBookmarks`, `isBookmarkDefined`) 72 | - Constants: UPPER_SNAKE_CASE (e.g., `NO_BOOKMARK_DEFINED`, `UNTITLED_SCHEME`) 73 | - Private class members: camelCase without underscore prefix 74 | 75 | ### Architecture Patterns 76 | - **Controller Pattern**: `Controller` class manages files and bookmarks per workspace folder 77 | - **File Pattern**: `File` class represents a document with its bookmarks 78 | - **Bookmark Pattern**: `Bookmark` interface with line and column positions 79 | - **Sticky Engine**: Two implementations (legacy and new) for maintaining bookmark positions during edits 80 | - **Decoration Pattern**: Separate decoration types for gutter icons and line backgrounds 81 | 82 | ## Extension Features to Maintain 83 | 84 | 1. **Toggle Bookmarks**: Commands 0-9 to mark/unmark positions 85 | 2. **Jump to Bookmarks**: Commands 0-9 to navigate to marked positions 86 | 3. **List Commands**: Show bookmarks from current file or all files 87 | 4. **Clear Commands**: Remove bookmarks from current file or all files 88 | 5. **Multi-root Workspace Support**: Manage bookmarks per workspace folder 89 | 6. **Remote Development Support**: Works with Docker, SSH, WSL 90 | 7. **Sticky Bookmarks**: Maintain positions during code edits 91 | 8. **Customizable Appearance**: Gutter icons, line backgrounds, colors 92 | 9. **Persistence**: Save bookmarks in workspace state or project files 93 | 94 | ## Configuration Settings 95 | 96 | Key settings in `package.json`: 97 | - `saveBookmarksInProject`: Save in `.vscode/numbered-bookmarks.json` 98 | - `experimental.enableNewStickyEngine`: Use new sticky engine (default: true) 99 | - `keepBookmarksOnLineDelete`: Move bookmarks to next line on delete 100 | - `showBookmarkNotDefinedWarning`: Show warning when bookmark not defined 101 | - `navigateThroughAllFiles`: How to navigate across files (false/replace/allowDuplicates) 102 | - `gutterIconFillColor`: Gutter icon background color 103 | - `gutterIconNumberColor`: Gutter icon number color 104 | - `revealLocation`: Where to reveal bookmarked line (top/center) 105 | 106 | ## Testing Guidelines 107 | 108 | - Use Mocha test framework with `suite` and `test` functions 109 | - Import VS Code API for extension testing 110 | - Test extension activation and core functionality 111 | - Use async/await for asynchronous operations 112 | - Include timeout utilities for async tests 113 | 114 | ## Important Notes 115 | 116 | - **Bookmark #0**: Reactivated but has no keyboard shortcut due to OS limitations 117 | - **macOS Shortcuts**: Use `Cmd` instead of `Ctrl` for some shortcuts (Cmd+Shift+3, Cmd+Shift+4) 118 | - **Untitled Files**: Special handling for untitled documents 119 | - **Path Handling**: Cross-platform path handling (Windows backslashes vs. Unix forward slashes) 120 | - **Localization**: Support for multiple languages via l10n and package.nls.*.json files 121 | - **Walkthrough**: Extension includes a getting started walkthrough 122 | 123 | ## Dependencies 124 | 125 | ### Production 126 | - `vscode-ext-codicons`: Codicon support 127 | - `vscode-ext-decoration`: Decoration utilities 128 | - `os-browserify`, `path-browserify`: Polyfills for browser compatibility 129 | 130 | ### Development 131 | - ESLint with `eslint-config-vscode-ext` 132 | - TypeScript ^4.4.4 133 | - Webpack 5 with ts-loader and terser plugin 134 | 135 | ## Common Tasks 136 | 137 | ### Adding a New Command 138 | 1. Add command definition in `package.json` > `contributes.commands` 139 | 2. Add localized string in `package.nls.json` 140 | 3. Register command handler in `src/extension.ts` 141 | 4. Add keyboard binding if needed in `package.json` > `contributes.keybindings` 142 | 5. Update menu contributions if applicable 143 | 144 | ### Modifying Bookmark Behavior 145 | - Core logic in `src/core/operations.ts` 146 | - Sticky behavior in `src/sticky/sticky.ts` or `src/sticky/stickyLegacy.ts` 147 | - Visual updates in `src/decoration/decoration.ts` 148 | 149 | ### Adding Tests 150 | - Create test files in `src/test/suite/` 151 | - Follow existing test patterns with Mocha 152 | - Update test suite index if needed 153 | 154 | ## External Resources 155 | 156 | - Extension published to VS Code Marketplace and Open VSX 157 | - Documentation in README.md 158 | - Changelog in CHANGELOG.md 159 | - Submodule: vscode-whats-new for What's New feature 160 | -------------------------------------------------------------------------------- /src/core/controller.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import os = require("os"); 7 | import path = require("path"); 8 | import { Uri, WorkspaceFolder } from "vscode"; 9 | import { File } from "./file"; 10 | import { getFileUri, getRelativePath } from "../utils/fs"; 11 | import { createFile } from "./file"; 12 | import { isBookmarkDefined } from "./operations"; 13 | import { UNTITLED_SCHEME } from "./constants"; 14 | 15 | export class Controller { 16 | public files: File[]; 17 | public workspaceFolder: WorkspaceFolder | undefined; 18 | 19 | constructor(workspaceFolder: WorkspaceFolder | undefined) { 20 | this.workspaceFolder = workspaceFolder; 21 | this.files = []; 22 | } 23 | 24 | public loadFrom(jsonObject, relativePath?) { 25 | if (jsonObject === "") { 26 | return; 27 | } 28 | 29 | // OLD format 30 | if ((jsonObject.bookmarks)) { 31 | const jsonBookmarks = jsonObject.bookmarks; 32 | // tslint:disable-next-line: prefer-for-of 33 | for (let idx = 0; idx < jsonBookmarks.length; idx++) { 34 | const jsonBookmark = jsonBookmarks[ idx ]; 35 | 36 | // untitled files, ignore (for now) 37 | const ff = jsonBookmark.fsPath; 38 | if (ff.match(/Untitled-\d+/)) { 39 | continue; 40 | } 41 | 42 | const file = relativePath 43 | ? createFile(jsonBookmark.fsPath.replace(`$ROOTPATH$${path.sep}`, "")) 44 | : createFile(getRelativePath(this.workspaceFolder?.uri?.fsPath, jsonBookmark.fsPath)); 45 | 46 | // Win32 uses `\\` but uris always uses `/` 47 | if (os.platform() === "win32") { 48 | file.path = file.path.replace(/\\/g, "/"); 49 | } 50 | 51 | const bmksPosition = jsonBookmark.bookmarks.map(bmk => { 52 | return {line: bmk, column: 0} 53 | }); 54 | file.bookmarks = [ 55 | ...bmksPosition 56 | ] 57 | this.files.push(file); 58 | } 59 | } else { // NEW format 60 | for (const file of jsonObject.files) { 61 | 62 | const bookmark = createFile(file.path);//??, file.uri ? file.uri : undefined); 63 | bookmark.bookmarks = [ 64 | ...file.bookmarks 65 | ] 66 | this.files.push(bookmark); 67 | } 68 | } 69 | } 70 | 71 | public fromUri(uri: Uri) { 72 | if (uri.scheme === UNTITLED_SCHEME) { 73 | for (const file of this.files) { 74 | if (file.uri?.toString() === uri.toString()) { 75 | return file; 76 | } 77 | } 78 | return; 79 | } 80 | 81 | const uriPath = !this.workspaceFolder 82 | ? uri.path 83 | : getRelativePath(this.workspaceFolder.uri.path, uri.path); 84 | 85 | for (const file of this.files) { 86 | if (file.path === uriPath) { 87 | return file; 88 | } 89 | } 90 | } 91 | 92 | public indexFromPath(filePath: string) { 93 | for (let index = 0; index < this.files.length; index++) { 94 | const file = this.files[index]; 95 | 96 | if (file.path === filePath) { 97 | return index; 98 | } 99 | } 100 | } 101 | 102 | public addFile(uri: Uri) { 103 | if (uri.scheme === UNTITLED_SCHEME) { 104 | // let foundFile: File; 105 | // for (const file of this.files) { 106 | // if (file.uri?.toString() === uri.toString()) { 107 | // foundFile = file; 108 | // } 109 | // } 110 | 111 | // if (!foundFile) { 112 | const file = createFile(uri.path, uri); 113 | this.files.push(file); 114 | // } 115 | return; 116 | } 117 | 118 | const uriPath = !this.workspaceFolder 119 | ? uri.path 120 | : getRelativePath(this.workspaceFolder.uri.path, uri.path); 121 | 122 | const paths = this.files.map(file => file.path); 123 | if (paths.indexOf(uriPath) < 0) { 124 | const bookmark = createFile(uriPath); 125 | this.files.push(bookmark); 126 | } 127 | } 128 | 129 | public getFileUri(file: File) { 130 | return getFileUri(file, this.workspaceFolder); 131 | } 132 | 133 | public zip(): Controller { 134 | function isNotEmpty(book: File): boolean { 135 | let hasAny = false; 136 | for (const element of book.bookmarks) { 137 | hasAny = isBookmarkDefined(element); 138 | if (hasAny) { 139 | break; 140 | } 141 | } 142 | return hasAny; 143 | } 144 | 145 | function isValid(file: File): boolean { 146 | return !file.uri ; // Untitled files 147 | } 148 | 149 | function canBeSaved(file: File): boolean { 150 | return isValid(file) && isNotEmpty(file); 151 | } 152 | 153 | const newBookmarks: Controller = new Controller(this.workspaceFolder); 154 | newBookmarks.files = JSON.parse(JSON.stringify(this.files)).filter(canBeSaved); 155 | 156 | delete newBookmarks.workspaceFolder; 157 | 158 | return newBookmarks; 159 | } 160 | 161 | public updateFilePath(oldFilePath: string, newFilePath: string): void { 162 | for (const file of this.files) { 163 | if (file.path === oldFilePath) { 164 | file.path = newFilePath; 165 | break; 166 | } 167 | } 168 | } 169 | 170 | public updateDirectoryPath(oldDirectoryPath: string, newDirectoryPath: string): void { 171 | for (const file of this.files) { 172 | if (file.path.startsWith(oldDirectoryPath)) { 173 | file.path = file.path.replace(oldDirectoryPath, newDirectoryPath); 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/whats-new/contentProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { ChangeLogItem, ChangeLogKind, ContentProvider, Header, Image, Sponsor, IssueKind, SupportChannel, SocialMediaProvider } from "../../vscode-whats-new/src/ContentProvider"; 7 | 8 | export class NumberedBookmarksContentProvider implements ContentProvider { 9 | public provideHeader(logoUrl: string): Header { 10 | return
{logo: {src: logoUrl, height: 50, width: 50}, 11 | message: `Numbered Bookmarks helps you to navigate in your code, moving 12 | between important positions easily and quickly. No more need 13 | to search for code. All of this in Delphi style`}; 14 | } 15 | 16 | public provideChangeLog(): ChangeLogItem[] { 17 | const changeLog: ChangeLogItem[] = []; 18 | 19 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "9.0.0", releaseDate: "November 2025" } }); 20 | changeLog.push({ 21 | kind: ChangeLogKind.NEW, 22 | detail: { 23 | message: "Fully Open Source again", 24 | id: 131, 25 | kind: IssueKind.Issue 26 | } 27 | }); 28 | changeLog.push({ 29 | kind: ChangeLogKind.FIXED, 30 | detail: { 31 | message: "Reuse opened file", 32 | id: 180, 33 | kind: IssueKind.Issue 34 | } 35 | }); 36 | changeLog.push({ 37 | kind: ChangeLogKind.INTERNAL, 38 | detail: { 39 | message: "Security Alert: word-wrap", 40 | id: 167, 41 | kind: IssueKind.PR, 42 | kudos: "dependabot" 43 | } 44 | }); 45 | changeLog.push({ 46 | kind: ChangeLogKind.INTERNAL, 47 | detail: { 48 | message: "Security Alert: webpack", 49 | id: 178, 50 | kind: IssueKind.PR, 51 | kudos: "dependabot" 52 | } 53 | }); 54 | changeLog.push({ 55 | kind: ChangeLogKind.INTERNAL, 56 | detail: { 57 | message: "Security Alert: serialize-javascript", 58 | id: 183, 59 | kind: IssueKind.PR, 60 | kudos: "dependabot" 61 | } 62 | }); 63 | changeLog.push({ 64 | kind: ChangeLogKind.INTERNAL, 65 | detail: { 66 | message: "Security Alert: braces", 67 | id: 176, 68 | kind: IssueKind.PR, 69 | kudos: "dependabot" 70 | } 71 | }); 72 | 73 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "8.5.0", releaseDate: "March 2024" } }); 74 | changeLog.push({ 75 | kind: ChangeLogKind.NEW, 76 | detail: { 77 | message: "Published to Open VSX", 78 | id: 147, 79 | kind: IssueKind.Issue 80 | } 81 | }); 82 | changeLog.push({ 83 | kind: ChangeLogKind.NEW, 84 | detail: { 85 | message: "New setting to choose viewport position on navigation", 86 | id: 141, 87 | kind: IssueKind.Issue 88 | } 89 | }); 90 | 91 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "8.4.0", releaseDate: "June 2023" } }); 92 | changeLog.push({ 93 | kind: ChangeLogKind.NEW, 94 | detail: { 95 | message: "Add Getting Started/Walkthrough support", 96 | id: 117, 97 | kind: IssueKind.Issue 98 | } 99 | }); 100 | changeLog.push({ 101 | kind: ChangeLogKind.NEW, 102 | detail: { 103 | message: "Add Localization (l10n) support", 104 | id: 151, 105 | kind: IssueKind.Issue 106 | } 107 | }); 108 | changeLog.push({ 109 | kind: ChangeLogKind.CHANGED, 110 | detail: { 111 | message: "Avoid What's New when using Gitpod", 112 | id: 168, 113 | kind: IssueKind.Issue 114 | } 115 | }); 116 | changeLog.push({ 117 | kind: ChangeLogKind.CHANGED, 118 | detail: { 119 | message: "Avoid What's New when installing lower versions", 120 | id: 168, 121 | kind: IssueKind.Issue 122 | } 123 | }); 124 | changeLog.push({ 125 | kind: ChangeLogKind.FIXED, 126 | detail: { 127 | message: "Repeated gutter icon on line wrap", 128 | id: 149, 129 | kind: IssueKind.Issue 130 | } 131 | }); 132 | changeLog.push({ 133 | kind: ChangeLogKind.INTERNAL, 134 | detail: { 135 | message: "Improve Startup speed", 136 | id: 145, 137 | kind: IssueKind.Issue 138 | } 139 | }); 140 | changeLog.push({ 141 | kind: ChangeLogKind.INTERNAL, 142 | detail: { 143 | message: "Security Alert: webpack", 144 | id: 156, 145 | kind: IssueKind.PR, 146 | kudos: "dependabot" 147 | } 148 | }); 149 | changeLog.push({ 150 | kind: ChangeLogKind.INTERNAL, 151 | detail: { 152 | message: "Security Alert: terser", 153 | id: 143, 154 | kind: IssueKind.PR, 155 | kudos: "dependabot" 156 | } 157 | }); 158 | 159 | 160 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "8.3.1", releaseDate: "June 2022" } }); 161 | changeLog.push({ 162 | kind: ChangeLogKind.INTERNAL, 163 | detail: "Add GitHub Sponsors support" 164 | }); 165 | 166 | changeLog.push({ kind: ChangeLogKind.VERSION, detail: { releaseNumber: "8.3.0", releaseDate: "April 2022" } }); 167 | changeLog.push({ 168 | kind: ChangeLogKind.NEW, 169 | detail: { 170 | message: "New setting to decide if should delete bookmark if associated line is deleted", 171 | id: 27, 172 | kind: IssueKind.Issue 173 | } 174 | }); 175 | changeLog.push({ 176 | kind: ChangeLogKind.NEW, 177 | detail: { 178 | message: "Update bookmark reference on file renames", 179 | id: 120, 180 | kind: IssueKind.Issue 181 | } 182 | }); 183 | changeLog.push({ 184 | kind: ChangeLogKind.CHANGED, 185 | detail: { 186 | message: "Replace custom icons with on the fly approach", 187 | id: 129, 188 | kind: IssueKind.Issue 189 | } 190 | }); 191 | 192 | return changeLog; 193 | } 194 | 195 | public provideSupportChannels(): SupportChannel[] { 196 | const supportChannels: SupportChannel[] = []; 197 | supportChannels.push({ 198 | title: "Become a sponsor on GitHub", 199 | link: "https://github.com/sponsors/alefragnani", 200 | message: "Become a Sponsor" 201 | }); 202 | supportChannels.push({ 203 | title: "Donate via PayPal", 204 | link: "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=EP57F3B6FXKTU&lc=US&item_name=Alessandro%20Fragnani&item_number=vscode%20extensions¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted", 205 | message: "Donate via PayPal" 206 | }); 207 | return supportChannels; 208 | } 209 | } 210 | 211 | export class NumberedBookmarksSocialMediaProvider implements SocialMediaProvider { 212 | public provideSocialMedias() { 213 | return [{ 214 | title: "Follow me on Twitter", 215 | link: "https://www.twitter.com/alefragnani" 216 | }]; 217 | } 218 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Numbered Bookmarks Logo 4 |

5 | 6 | # What's new in Numbered Bookmarks 9 7 | 8 | * Fully Open Source again 9 | * Published to **Open VSX** 10 | * Adds **Getting Started / Walkthrough** 11 | * Adds **Rename file** support 12 | * New **Sticky Engine** 13 | 14 | # Support 15 | 16 | **Numbered Bookmarks** is an extension created for **Visual Studio Code**. If you find it useful, please consider supporting it. 17 | 18 | 19 | 20 | 23 | 26 | 29 | 30 |
21 | 22 | 24 | 25 | 27 | 28 |
31 | 32 | # Numbered Bookmarks 33 | 34 | It helps you to navigate in your code, moving between important positions easily and quickly. No more need to _search for code_. All of this in **_in Delphi style_**. 35 | 36 | # Features 37 | 38 | ## Available commands 39 | 40 | * `Numbered Bookmarks: Toggle Bookmark '#number'` Mark/unmark the current position with a numbered bookmark 41 | * `Numbered Bookmarks: Jump to Bookmark '#number'` Move the cursor to the numbered bookmark 42 | * `Numbered Bookmarks: List` List all bookmarks from the current file 43 | * `Numbered Bookmarks: List from All Files` List all bookmarks from the all files 44 | * `Numbered Bookmarks: Clear` remove all bookmarks from the current file 45 | * `Numbered Bookmarks: Clear from All Files` remove all bookmarks from the all files 46 | 47 | > Both **Toggle Bookmark** and **Jump to Bookmark** commands are numbered from 0 to 9 48 | 49 | > The Numbered Bookmark **0** has been reactivated in [PR #16](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/16), but because of [this issue](https://github.com/Microsoft/vscode/issues/2585) it has no _keyboard shortcut_ defined. If sometime in the future these OS related limitation disappears, the shortcuts will be restored. 50 | 51 | > MacOS users should be aware that some commands shortcuts should conflict with native shortcuts, and uses `Cmd` instead of `Ctrl` (`Cmd + Shift + 3` and `Cmd + Shift + 4`) 52 | 53 | ## Manage your bookmarks 54 | 55 | ### Toggle Bookmark '#number' 56 | 57 | You can easily Mark/Unmark bookmarks on any position. 58 | 59 | ![Toggle](images/numbered-bookmarks-toggle.png) 60 | 61 | > The default shortcuts are numbered from 0 to 9: `Toggle Bookmark #` (`Ctrl + Shift + #`) 62 | 63 | ### Navigation 64 | 65 | ### Jump to Bookmark '#number' 66 | 67 | > The default shortcuts are numbered from 0 to 9: `Jump to Bookmark #` (`Ctrl + #`) 68 | 69 | ### List 70 | 71 | List all bookmarks from the current file and easily navigate to any one. It shows you the line contents and temporarily scroll to that position. 72 | 73 | ### List from All Files 74 | 75 | List all bookmarks from all files and easily navigate to any one. It shows you the line contents and temporarily scroll to that position. 76 | 77 | ![List](images/numbered-bookmarks-list-from-all-files.gif) 78 | 79 | * Bookmarks from the active file shows the line content and the position 80 | * Bookmarks from other files also shows the relative file path 81 | 82 | ### Improved Multi-root support 83 | 84 | When you work with **multi-root** workspaces, the extension can manage the bookmarks individually for each folder. 85 | 86 | Simply define `saveBookmarksInProject` as `true` on your **User Settings** or in the **Workspace Settings**, and when you run the `Numbered Bookmarks: List from All Files` command, you will be able to select from which folder the bookmarks will be shown. 87 | 88 | ![List](images/numbered-bookmarks-list-from-all-files-multi-root.gif) 89 | 90 | ### Remote Development support 91 | 92 | The extension now fully supports **Remote Development** scenarios. 93 | 94 | It means that when you connect to a _remote_ location, like a Docker Container, SSH or WSL, the extension will be available, ready to be used. 95 | 96 | > You don't need to install the extension on the remote anymore. 97 | 98 | Better yet, if you use `numberedBookmarks.saveBookmarksInProject` setting defined as `true`, the bookmarks saved locally _will be available_ remotely, and you will be able to navigate and update the bookmarks. Just like it was a resource from folder you opened remotely. 99 | 100 | ## Available Settings 101 | 102 | * Bookmarks are always saved between sessions, and you can decide if it should be saved _in the Project_, so you can add it to your Git/SVN repo and have it in all your machines _(`false` by default)_. Set to `true` and it will save the bookmarks in `.vscode\numbered-bookmarks.json` file. 103 | ```json 104 | "numberedBookmarks.saveBookmarksInProject": true 105 | ``` 106 | 107 | * Controls whether to show a warning when a bookmark is not defined _(`false` by default)_ 108 | ```json 109 | "numberedBookmarks.showBookmarkNotDefinedWarning": true 110 | ``` 111 | 112 | * Per [User Requests](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/6) it is now possible to choose how Bookmarks _Navigate Through All Files_: 113 | 114 | ```json 115 | "numberedBookmarks.navigateThroughAllFiles" 116 | ``` 117 | 118 | Possible Values: 119 | 120 | Value | Explanation 121 | --------- | --------- 122 | `false` | _default_ - same behavior as today 123 | `replace` | you can't have the same numbered bookmark in different files 124 | `allowDuplicates` | you can have the same numbered bookmark in different files, and if you jump repeatedly to the same number, it will look on other files 125 | 126 | * **Experimental**. Enables the new **Sticky engine** with support for Formatters, improved source change detections and undo operations _(`true` by default)_ 127 | 128 | ```json 129 | "numberedBookmarks.experimental.enableNewStickyEngine": false 130 | ``` 131 | 132 | * "Specifies whether bookmarks on deleted line should be kept on file, moving it down to the next line, instead of deleting it with the line where it was toggled." _(`false` by default)_ 133 | 134 | ```json 135 | "numberedBookmarks.keepBookmarksOnLineDelete": true 136 | ``` 137 | 138 | > **Limitation:** It does not support `Undo` operations. It means that, once you delete a line and the bookmark is moved to the next available line, the `Undo` operation won't move the bookmark back to the previous line. The next line is now the new location of the bookmark. 139 | 140 | * Choose the gutter icon fill color 141 | 142 | ```json 143 | "numberedBookmarks.gutterIconFillColor" 144 | ``` 145 | 146 | * Choose the gutter icon number color 147 | 148 | ```json 149 | "numberedBookmarks.gutterIconNumberColor" 150 | ``` 151 | 152 | * Choose the location where the bookmarked line will be revealed _(`center` by default)_ 153 | 154 | * `top`: Reveals the bookmarked line at the top of the editor 155 | * `center`: Reveals the bookmarked line in the center of the editor 156 | 157 | ```json 158 | "numberedBookmarks.revealPosition": "center" 159 | ``` 160 | 161 | ## Available Colors 162 | 163 | * Choose the background color to use on a bookmarked line 164 | ```json 165 | "workbench.colorCustomizations": { 166 | "numberedBookmarks.lineBackground": "#157EFB22" 167 | } 168 | ``` 169 | 170 | * Choose the border color to use on a bookmarked line 171 | ```json 172 | "workbench.colorCustomizations": { 173 | "numberedBookmarks.lineBorder": "#FF0000" 174 | } 175 | ``` 176 | 177 | * Choose marker color to use in the overview ruler 178 | ```json 179 | "workbench.colorCustomizations": { 180 | "numberedBookmarks.overviewRuler": "#157EFB88" 181 | } 182 | ``` 183 | 184 | 185 | > For any of the _Color_ settings, you can use color names `blue`, RGB `rgb(0, 255, 37)`, RGBA `rgba(0, 255, 37, 0.2)` or HEX `#00ff25` format. 186 | 187 | ## Project and Session Based 188 | 189 | The bookmarks are saved _per session_ for the project that you are using. You don't have to worry about closing files in _Working Files_. When you reopen the file, the bookmarks are restored. 190 | 191 | It also works even if you only _preview_ a file (simple click in TreeView). You can put bookmarks in any file and when you preview it again, the bookmarks will be there. 192 | 193 | # License 194 | 195 | [GPL-3.0](LICENSE.md) © Alessandro Fragnani -------------------------------------------------------------------------------- /src/sticky/stickyLegacy.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Castellant Guillaume & Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | * 5 | * Original Author: Castellant Guillaume (@Terminux), 6 | * (https://github.com/alefragnani/vscode-bookmarks/pull/20) 7 | *--------------------------------------------------------------------------------------------*/ 8 | 9 | import * as vscode from "vscode"; 10 | import { File } from "../core/file"; 11 | import { NO_BOOKMARK_DEFINED } from "../core/constants"; 12 | import { indexOfBookmark } from "../core/operations"; 13 | 14 | export class Sticky { 15 | 16 | public static stickyBookmarks(event: vscode.TextDocumentChangeEvent, activeEditorCountLine: number, activeBookmark: File, activeEditor: vscode.TextEditor): boolean { 17 | 18 | let diffLine: number; 19 | let updatedBookmark = false; 20 | 21 | // fix autoTrimWhitespace 22 | // if (event.contentChanges.length === 1) { 23 | if (this.hadOnlyOneValidContentChange(event)) { 24 | // add or delete line case 25 | if (event.document.lineCount !== activeEditorCountLine) { 26 | if (event.document.lineCount > activeEditorCountLine) { 27 | diffLine = event.document.lineCount - activeEditorCountLine; 28 | } else if (event.document.lineCount < activeEditorCountLine) { 29 | diffLine = activeEditorCountLine - event.document.lineCount; 30 | diffLine = 0 - diffLine; 31 | 32 | // one line up 33 | if (event.contentChanges[ 0 ].range.end.line - event.contentChanges[ 0 ].range.start.line === 1) { 34 | 35 | if ((event.contentChanges[ 0 ].range.end.character === 0) && 36 | (event.contentChanges[ 0 ].range.start.character === 0)) { 37 | // the bookmarked one 38 | const idxbk = indexOfBookmark(activeBookmark, event.contentChanges[ 0 ].range.start.line); 39 | // const idxbk = activeBookmark.bookmarks.indexOf(event.contentChanges[ 0 ].range.start.line); 40 | if (idxbk > -1) { 41 | activeBookmark.bookmarks[ idxbk ] = NO_BOOKMARK_DEFINED; 42 | } 43 | } 44 | } 45 | 46 | if (event.contentChanges[ 0 ].range.end.line - event.contentChanges[ 0 ].range.start.line > 1) { 47 | for (let i = event.contentChanges[ 0 ].range.start.line/* + 1*/; i <= event.contentChanges[ 0 ].range.end.line; i++) { 48 | const index = indexOfBookmark(activeBookmark, i); 49 | // const index = activeBookmark.bookmarks.indexOf(i); 50 | 51 | if (index > -1) { 52 | activeBookmark.bookmarks[ index ] = NO_BOOKMARK_DEFINED; 53 | updatedBookmark = true; 54 | } 55 | } 56 | } 57 | } 58 | 59 | // for (let index in activeBookmark.bookmarks) { 60 | for (let index = 0; index < activeBookmark.bookmarks.length; index++) { 61 | const eventLine = event.contentChanges[ 0 ].range.start.line; 62 | let eventcharacter = event.contentChanges[ 0 ].range.start.character; 63 | 64 | // indent ? 65 | if (eventcharacter > 0) { 66 | let textInEventLine = activeEditor.document.lineAt(eventLine).text; 67 | textInEventLine = textInEventLine.replace(/\t/g, "").replace(/\s/g, ""); 68 | if (textInEventLine === "") { 69 | eventcharacter = 0; 70 | } 71 | } 72 | 73 | // also = 74 | if ( 75 | ((activeBookmark.bookmarks[ index ].line > eventLine) && (eventcharacter > 0)) || 76 | ((activeBookmark.bookmarks[ index ].line >= eventLine) && (eventcharacter === 0)) 77 | ) { 78 | let newLine = activeBookmark.bookmarks[ index ].line + diffLine; 79 | if (newLine < 0) { 80 | newLine = 0; 81 | } 82 | 83 | activeBookmark.bookmarks[ index ].line = newLine; 84 | updatedBookmark = true; 85 | } 86 | } 87 | } 88 | 89 | // paste case 90 | if (!updatedBookmark && (event.contentChanges[ 0 ].text.length > 1)) { 91 | const selection = vscode.window.activeTextEditor.selection; 92 | const lineRange = [ selection.start.line, selection.end.line ]; 93 | let lineMin = Math.min.apply(this, lineRange); 94 | let lineMax = Math.max.apply(this, lineRange); 95 | 96 | if (selection.start.character > 0) { 97 | lineMin++; 98 | } 99 | 100 | if (selection.end.character < vscode.window.activeTextEditor.document.lineAt(selection.end).range.end.character) { 101 | lineMax--; 102 | } 103 | 104 | if (lineMin <= lineMax) { 105 | for (let i = lineMin; i <= lineMax; i++) { 106 | const index = activeBookmark.bookmarks.indexOf(i); 107 | if (index > -1) { 108 | activeBookmark.bookmarks[ index ] = NO_BOOKMARK_DEFINED; 109 | updatedBookmark = true; 110 | } 111 | } 112 | } 113 | } 114 | } else if (event.contentChanges.length === 2) { 115 | // move line up and move line down case 116 | if (activeEditor.selections.length === 1) { 117 | if (event.contentChanges[ 0 ].text === "") { 118 | updatedBookmark = this.moveStickyBookmarks("down", activeBookmark, activeEditor); 119 | } else if (event.contentChanges[ 1 ].text === "") { 120 | updatedBookmark = this.moveStickyBookmarks("up", activeBookmark, activeEditor); 121 | } 122 | } 123 | } 124 | 125 | return updatedBookmark; 126 | } 127 | 128 | private static moveStickyBookmarks(direction: string, activeBookmark: File, activeEditor: vscode.TextEditor): boolean { 129 | let diffChange = -1; 130 | let updatedBookmark = false; 131 | let diffLine; 132 | const selection = activeEditor.selection; 133 | let lineRange = [ selection.start.line, selection.end.line ]; 134 | const lineMin = Math.min.apply(this, lineRange); 135 | let lineMax = Math.max.apply(this, lineRange); 136 | 137 | if (selection.end.character === 0 && !selection.isSingleLine) { 138 | // const lineAt = activeEditor.document.lineAt(selection.end.line); 139 | // const posMin = new vscode.Position(selection.start.line + 1, selection.start.character); 140 | // const posMax = new vscode.Position(selection.end.line, lineAt.range.end.character); 141 | // vscode.window.activeTextEditor.selection = new vscode.Selection(posMin, posMax); 142 | lineMax--; 143 | } 144 | 145 | if (direction === "up") { 146 | diffLine = 1; 147 | 148 | const index = indexOfBookmark(activeBookmark, lineMin - 1); 149 | // const index = activeBookmark.bookmarks.indexOf(lineMin - 1); 150 | if (index > -1) { 151 | diffChange = lineMax; 152 | activeBookmark.bookmarks[ index ] = NO_BOOKMARK_DEFINED; 153 | updatedBookmark = true; 154 | } 155 | } else if (direction === "down") { 156 | diffLine = -1; 157 | 158 | const index = activeBookmark.bookmarks.indexOf(lineMax + 1); 159 | if (index > -1) { 160 | diffChange = lineMin; 161 | activeBookmark.bookmarks[ index ] = NO_BOOKMARK_DEFINED; 162 | updatedBookmark = true; 163 | } 164 | } 165 | 166 | lineRange = []; 167 | for (let i = lineMin; i <= lineMax; i++) { 168 | lineRange.push(i); 169 | } 170 | lineRange = lineRange.sort(); 171 | if (diffLine < 0) { 172 | lineRange = lineRange.reverse(); 173 | } 174 | 175 | // tslint:disable-next-line: forin 176 | for (const i in lineRange) { 177 | const index = indexOfBookmark(activeBookmark, lineRange[ i ]); 178 | // const index = activeBookmark.bookmarks.indexOf(lineRange[ i ]); 179 | if (index > -1) { 180 | activeBookmark.bookmarks[ index ].line -= diffLine; 181 | updatedBookmark = true; 182 | } 183 | } 184 | 185 | if (diffChange > -1) { 186 | //?? activeBookmark.bookmarks.push(diffChange); 187 | activeBookmark.bookmarks.push({line: diffChange, column: 0}); 188 | updatedBookmark = true; 189 | } 190 | 191 | return updatedBookmark; 192 | } 193 | 194 | private static hadOnlyOneValidContentChange(event: vscode.TextDocumentChangeEvent): boolean { 195 | 196 | // not valid 197 | if ((event.contentChanges.length > 2) || (event.contentChanges.length === 0)) { 198 | return false; 199 | } 200 | 201 | // normal behavior - only 1 202 | if (event.contentChanges.length === 1) { 203 | return true; 204 | } else { // has 2, but is it a trimAutoWhitespace issue? 205 | if (event.contentChanges.length === 2) { 206 | const trimAutoWhitespace: boolean = vscode.workspace.getConfiguration("editor").get("trimAutoWhitespace", true); 207 | if (!trimAutoWhitespace) { 208 | return false; 209 | } 210 | 211 | // check if the first range is 'equal' and if the second is 'empty' 212 | let fistRangeEquals: boolean = 213 | (event.contentChanges[ 0 ].range.start.character === event.contentChanges[ 0 ].range.end.character) && 214 | (event.contentChanges[ 0 ].range.start.line === event.contentChanges[ 0 ].range.end.line); 215 | 216 | let secondRangeEmpty: boolean = (event.contentChanges[ 1 ].text === "") && 217 | (event.contentChanges[ 1 ].range.start.line === event.contentChanges[ 1 ].range.end.line) && 218 | (event.contentChanges[ 1 ].range.start.character === 0) && 219 | (event.contentChanges[ 1 ].range.end.character > 0); 220 | 221 | if (fistRangeEquals && secondRangeEmpty) { 222 | return true; 223 | } else { 224 | fistRangeEquals = 225 | (event.contentChanges[ 0 ].rangeLength > 0) && 226 | (event.contentChanges[ 0 ].text === ""); 227 | secondRangeEmpty = 228 | (event.contentChanges[ 1 ].rangeLength === 0) && 229 | (event.contentChanges[ 1 ].text === "\r\n"); 230 | 231 | return fistRangeEquals && secondRangeEmpty; 232 | } 233 | } 234 | } 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src/sticky/sticky.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Castellant Guillaume & Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | * 5 | * Original Author: Castellant Guillaume (@Terminux), 6 | * (https://github.com/alefragnani/vscode-bookmarks/pull/20) 7 | *--------------------------------------------------------------------------------------------*/ 8 | 9 | import * as vscode from "vscode"; 10 | import { NO_BOOKMARK_DEFINED } from "../core/constants"; 11 | import { Controller } from "../core/controller"; 12 | import { File } from "../core/file"; 13 | import { indexOfBookmark } from "../core/operations"; 14 | 15 | export function updateStickyBookmarks(event: vscode.TextDocumentChangeEvent, 16 | activeBookmark: File, activeEditor: vscode.TextEditor, controller: Controller): boolean { 17 | 18 | // no changes at all 19 | if (hasNoChanges(event)) { 20 | return false; 21 | } 22 | 23 | // added an empty, indented, line 24 | if (isAddEmptyLineWithIndent(event, activeBookmark, activeEditor, controller)) { 25 | return true; 26 | } 27 | 28 | // just a Move Line UP / Down 29 | if (isMoveLineUpDown(event, activeBookmark, activeEditor, controller)) { 30 | return true; 31 | } 32 | 33 | let updatedBookmark = false; 34 | const keepBookmarksOnLineDelete = vscode.workspace.getConfiguration("numberedBookmarks").get("keepBookmarksOnLineDelete", false); 35 | 36 | // the NEW Sticky Engine 37 | for (const contentChangeEvent of event.contentChanges) { 38 | 39 | // didn't DEL neither ADD lines 40 | if (!isDeleteLine(contentChangeEvent) && !isAddLine(contentChangeEvent)) { 41 | continue; 42 | } 43 | 44 | if (isAddLine(contentChangeEvent)) { 45 | const numberOfLinesAdded = (contentChangeEvent.text.match(/\n/g) || []).length; 46 | 47 | for (let index = 0; index < activeBookmark.bookmarks.length; index++) { 48 | const eventLine: number = contentChangeEvent.range.start.line; 49 | let eventcharacter: number = contentChangeEvent.range.start.character; 50 | 51 | // indent ? 52 | if (eventcharacter > 0) { 53 | let textInEventLine = activeEditor.document.lineAt(eventLine).text; 54 | textInEventLine = textInEventLine.replace(/\t/g, "").replace(/\s/g, ""); 55 | if (textInEventLine === "") { 56 | eventcharacter = 0; 57 | } 58 | } 59 | 60 | // also = 61 | if ( 62 | ((activeBookmark.bookmarks[ index ].line > eventLine) && (eventcharacter > 0)) || 63 | ((activeBookmark.bookmarks[ index ].line >= eventLine) && (eventcharacter === 0)) 64 | ) { 65 | const newLine = activeBookmark.bookmarks[ index ].line + numberOfLinesAdded; 66 | activeBookmark.bookmarks[index].line = newLine; 67 | updatedBookmark = true; 68 | } 69 | } 70 | } 71 | 72 | if (isDeleteLine(contentChangeEvent)) { 73 | 74 | // delete bookmarks INSIDE the deleted content 75 | for (let i = contentChangeEvent.range.start.line; i < contentChangeEvent.range.end.line; i++) { 76 | const index = indexOfBookmark(activeBookmark, i); 77 | 78 | if (index > -1) { 79 | if (keepBookmarksOnLineDelete) { 80 | const hasBookmarkAfterDeletionBlock = indexOfBookmark(activeBookmark, contentChangeEvent.range.end.line) > -1; 81 | if (!hasBookmarkAfterDeletionBlock) { 82 | const newLine = contentChangeEvent.range.end.line; 83 | activeBookmark.bookmarks[index].line = newLine; 84 | } else { 85 | activeBookmark.bookmarks[index] = NO_BOOKMARK_DEFINED; 86 | } 87 | } else { 88 | activeBookmark.bookmarks[index] = NO_BOOKMARK_DEFINED; 89 | } 90 | updatedBookmark = true; 91 | } 92 | } 93 | 94 | // move bookmarks UP 95 | const numberOfLinesDeleted = contentChangeEvent.range.end.line - contentChangeEvent.range.start.line; 96 | for (let index = 0; index < activeBookmark.bookmarks.length; index++) { 97 | // const element = activeBookmark.bookmarks[index]; 98 | const eventLine: number = contentChangeEvent.range.start.line; 99 | let eventcharacter: number = contentChangeEvent.range.start.character; 100 | 101 | // indent ? 102 | if (eventcharacter > 0) { 103 | let textInEventLine = activeEditor.document.lineAt(eventLine).text; 104 | textInEventLine = textInEventLine.replace(/\t/g, "").replace(/\s/g, ""); 105 | if (textInEventLine === "") { 106 | eventcharacter = 0; 107 | } 108 | } 109 | if ( 110 | ((activeBookmark.bookmarks[ index ].line > eventLine) && (eventcharacter > 0)) || 111 | ((activeBookmark.bookmarks[ index ].line >= eventLine) && (eventcharacter === 0)) 112 | ) { 113 | const newLine = activeBookmark.bookmarks[ index ].line - numberOfLinesDeleted; 114 | activeBookmark.bookmarks[index].line = newLine; 115 | updatedBookmark = true; 116 | } 117 | } 118 | } 119 | } 120 | 121 | return updatedBookmark; 122 | } 123 | 124 | function isAddLine(contentChangeEvent: vscode.TextDocumentContentChangeEvent) { 125 | return contentChangeEvent.text.includes("\n"); 126 | } 127 | 128 | function isDeleteLine(contentChangeEvent: vscode.TextDocumentContentChangeEvent) { 129 | return /* contentChangeEvent.text === "" && */(contentChangeEvent.range.start.line < contentChangeEvent.range.end.line); 130 | } 131 | 132 | function hasNoChanges(event: vscode.TextDocumentChangeEvent) { 133 | return event.contentChanges.length === 0; 134 | } 135 | 136 | function isAddEmptyLineWithIndent(event: vscode.TextDocumentChangeEvent, activeBookmark: File, activeEditor: vscode.TextEditor, controller: Controller) { 137 | if (event.contentChanges.length !== 2) { 138 | return false; 139 | } 140 | 141 | const firstEvent = event.contentChanges[0]; 142 | const firstEventIsExpectation = (firstEvent.range.start.line === firstEvent.range.end.line && 143 | firstEvent.range.start.character === firstEvent.range.end.character && 144 | firstEvent.text.match(/\n/g).length > 0) 145 | const secondEvent = event.contentChanges[1]; 146 | const secondEventIsExpectation = (secondEvent.range.start.line === secondEvent.range.end.line && 147 | secondEvent.range.start.character === 0 && 148 | secondEvent.range.end.character !== 0 && 149 | secondEvent.text === ""); 150 | if (firstEventIsExpectation && secondEventIsExpectation) { 151 | return moveStickyBookmarks("up", secondEvent.range, activeBookmark, controller); 152 | } else { 153 | return false; 154 | } 155 | } 156 | 157 | function isMoveLineUpDown(event: vscode.TextDocumentChangeEvent, activeBookmark: File, activeEditor: vscode.TextEditor, controller: Controller) { 158 | 159 | // move line up and move line down 160 | const moveChanges = event.contentChanges.filter(c => !(c.range.start.line === c.range.end.line && /^[\t ]*$/.test(c.text))); 161 | if (moveChanges.length === 2) { 162 | 163 | let updatedBookmark = false; 164 | 165 | // move line up and move line down case 166 | if (activeEditor.selections.length === 1) { 167 | if (moveChanges[ 0 ].text === "") { 168 | updatedBookmark = moveStickyBookmarks("down", moveChanges[ 1 ].range, activeBookmark, controller); 169 | } else if (moveChanges[ 1 ].text === "") { 170 | updatedBookmark = moveStickyBookmarks("up", moveChanges[ 0 ].range, activeBookmark, controller); 171 | } 172 | } 173 | return updatedBookmark; 174 | } 175 | 176 | // not that stable yet 177 | // if (event.contentChanges.length > 1 && moveChanges.length === 1) { 178 | // let updatedBookmark = false; 179 | // if (activeEditor.selections.length === 1) { 180 | // if (moveChanges[ 0 ].range.start.line === activeEditor.selections[0].start.line) { 181 | // updatedBookmark = moveStickyBookmarks("up", moveChanges[ 0 ].range, activeBookmark, activeEditor, controller); 182 | // } else { 183 | // updatedBookmark = moveStickyBookmarks("down", moveChanges[ 0 ].range, activeBookmark, activeEditor, controller); 184 | // } 185 | // } 186 | // return updatedBookmark; 187 | // } 188 | 189 | return false; 190 | } 191 | 192 | function moveStickyBookmarks(direction: string, range: vscode.Range, activeBookmark: File, controller: Controller): boolean { 193 | let diffChange = -1; 194 | let updatedBookmark = false; 195 | let diffLine; 196 | const selection = range;//activeEditor.selection; 197 | let lineRange = [ selection.start.line, selection.end.line ]; 198 | const lineMin = Math.min.apply(this, lineRange); 199 | let lineMax = Math.max.apply(this, lineRange); 200 | 201 | if (selection.end.character === 0 && !selection.isSingleLine) { 202 | // const lineAt = activeEditor.document.lineAt(selection.end.line); 203 | // const posMin = new vscode.Position(selection.start.line + 1, selection.start.character); 204 | // const posMax = new vscode.Position(selection.end.line, lineAt.range.end.character); 205 | // vscode.window.activeTextEditor.selection = new vscode.Selection(posMin, posMax); 206 | lineMax--; 207 | } 208 | 209 | let indexRemoved: number; 210 | if (direction === "up") { 211 | diffLine = 1; 212 | 213 | indexRemoved = indexOfBookmark(activeBookmark, lineMin - 1); 214 | if (indexRemoved > -1) { 215 | diffChange = lineMax; 216 | activeBookmark.bookmarks[ indexRemoved ] = NO_BOOKMARK_DEFINED; 217 | updatedBookmark = true; 218 | } 219 | } else if (direction === "down") { 220 | diffLine = -1; 221 | 222 | indexRemoved = indexOfBookmark(activeBookmark, lineMax + 1); 223 | if (indexRemoved > -1) { 224 | diffChange = lineMin; 225 | activeBookmark.bookmarks[ indexRemoved ] = NO_BOOKMARK_DEFINED; 226 | updatedBookmark = true; 227 | } 228 | } 229 | 230 | lineRange = []; 231 | for (let i = lineMin; i <= lineMax; i++) { 232 | lineRange.push(i); 233 | } 234 | lineRange = lineRange.sort(); 235 | if (diffLine < 0) { 236 | lineRange = lineRange.reverse(); 237 | } 238 | 239 | // tslint:disable-next-line: forin 240 | for (const i in lineRange) { 241 | const index = indexOfBookmark(activeBookmark, lineRange[ i ]); 242 | if (index > -1) { 243 | activeBookmark.bookmarks[ index ].line -= diffLine; 244 | updatedBookmark = true; 245 | } 246 | } 247 | 248 | if (diffChange > -1 && indexRemoved) { 249 | activeBookmark.bookmarks[ indexRemoved ] = {line: diffChange, column: 0}; 250 | updatedBookmark = true; 251 | } 252 | 253 | return updatedBookmark; 254 | } 255 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [9.0.0] - 2025-12-13 2 | ### Added 3 | - Fully Open Source again (issue [#131](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/131)) 4 | 5 | ### Fixed 6 | - Reuse opened file (issue [#180](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/180)) 7 | 8 | ### Internal 9 | - Security Alert: word-wrap (dependabot [PR #167](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/167)) 10 | - Security Alert: webpack (dependabot [PR #178](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/178)) 11 | - Security Alert: serialize-javascript (dependabot [PR #183](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/183)) 12 | - Security Alert: braces (dependabot [PR #176](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/176)) 13 | 14 | ## [8.5.0] - 2024-04-04 15 | ### Added 16 | - Published to Open VSX (issue [#147](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/147)) 17 | - New setting to choose viewport position on navigation (issue [#141](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/141)) 18 | 19 | ## [8.4.0] - 2023-07-19 20 | ### Added 21 | - Getting Started/Walkthrough (issue [#117](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/117)) 22 | - Localization (l10n) support (issue [#151](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/151)) 23 | 24 | ### Changed 25 | - Avoid What's New when using Gitpod (issue [#168](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/168)) 26 | - Avoid What's New when installing lower versions (issue [#168](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/168)) 27 | 28 | ### Fixed 29 | - Repeated gutter icon on line wrap (issue [#149](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/149)) 30 | 31 | ### Internal 32 | - Switch initialization to `onStartupFinished` API (issue [#145](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/145)) 33 | - Security Alert: webpack (dependabot [PR #156](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/156)) 34 | - Security Alert: terser (dependabot [PR #143](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/143)) 35 | 36 | 37 | ## [8.3.1] - 2022-07-17 38 | ### Internal 39 | - Add GitHub Sponsors support (PR [#142](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/142)) 40 | 41 | ## [8.3.0] - 2022-05-07 42 | ### Added 43 | - New setting to decide if should delete bookmark if associated line is deleted (issue [#27](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/27)) 44 | - Update bookmark reference on file renames (issue [#120](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/120)) 45 | 46 | ### Changed 47 | - Replace custom icons with _on the fly_ approach (issue [#129](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/129)) 48 | 49 | ## [8.2.0] - 2022-01-12 50 | ### Added 51 | - New **Sticky Engine** with improved support to Formatters, Multi-cursor and Undo operations (issue [#115](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/115)) 52 | 53 | ### Changed 54 | - Removed deprecated setting `backgroundLineColor` (issue [#116](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/116)) 55 | 56 | ### Fixed 57 | - Bookmarks removes on Undo (issue [#47](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/47)) 58 | 59 | ## [8.1.0] - 2021-06-09 60 | ### Added 61 | - Support **Virtual Workspaces** (issue [#107](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/107)) 62 | - Support **Workspace Trust** (issue [#108](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/108)) 63 | - Support Translation (issue [#112](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/112)) 64 | - Return to line/column when cancel List or List from All Files (issue [#96](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/96)) 65 | 66 | ### Internal 67 | - Security Alert: lodash (dependabot [PR #109](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/109)) 68 | - Security Alert: ssri (dependabot [PR #106](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/106)) 69 | - Security Alert: y18n (dependabot [PR #104](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/104)) 70 | 71 | ## [8.0.3] - 2021-03-20 72 | ### Fixed 73 | - Bookmarks on deleted/missing files breaks jumping (issue [#102](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/102)) 74 | - Running the contributed command: 'numberedBookmarks.toggleBookmark1' failed (issue [#100](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/100)) 75 | - Toggling bookmarks on Untitled documents does not work bug (issue [#99](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/99)) 76 | 77 | ## [8.0.2] - 2021-02-25 78 | ### Fixed 79 | - Command `Toggle` not found - loading empty workspace with random files (issue [#97](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/97)) 80 | 81 | ## [8.0.1] - 2021-02-15 82 | ### Fixed 83 | - Extension does not activate on VS Code 1.50 (issue [#98](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/98)) 84 | 85 | ## [8.0.0] - 2021-02-11 86 | ### Added 87 | - Improvements on multi-root support (issue [#92](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/92)) 88 | - Multi-platform support (issue [#94](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/94)) 89 | - Support Remote Development (issue [#63](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/63)) 90 | - Support Column position (issue [#14](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/14)) 91 | 92 | ### Fixed 93 | - Error using `Toggle Bookmark` command with `saveBookmarksInProject` (issue [#69](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/69)) 94 | 95 | ### Internal 96 | - Do not show welcome message if installed by Settings Sync (issue [#95](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/95)) 97 | 98 | ## [7.3.0] - 2021-01-12 99 | ### Added 100 | - Support submenu for editor commands (issue [#84](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/84)) 101 | - New setting to decide if should show a warning when a bookmark is not defined (issue [#73](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/73)) 102 | 103 | ### Fixed 104 | - Typo in extension's configuration title (issue [#89](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/89)) 105 | 106 | ### Internal 107 | - Shrink installation size (issue [#53](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/53)) 108 | - Update whats-new submodule API (issue [#85](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/85)) 109 | 110 | ## [7.2.0] - 2020-09-16 111 | ### Internal 112 | - Use `vscode-ext-codicons` package (issue [#80](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/80)) 113 | - Migrate from TSLint to ESLint (issue [#75](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/75)) 114 | 115 | ## [7.1.3] - 2020-08-05 116 | ### Fixed 117 | - Security Alert: elliptic (dependabot [PR #79](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/79)) 118 | - Security Alert: lodash (dependabot [PR #77](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/77)) 119 | 120 | ## [7.1.2] - 2020-06-20 121 | ### Fixed 122 | - Stars visibility on Marketplace (issue [#76](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/76)) 123 | 124 | ## [7.1.1] - 2020-05-10 125 | ### Fixed 126 | - View context menu displayed erroneously (issue [#72](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/72)) 127 | 128 | ## [7.1.0] - 2020-05-09 129 | ### Fixed 130 | - Navigation error on empty files (issue [#68](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/68)) 131 | 132 | ### Internal 133 | - Support VS Code extension view context menu 134 | 135 | ## [7.0.0] - 2020-02-07 136 | ### Added 137 | - Support `workbench.colorCustomizations` (issue [#61](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/61)) 138 | 139 | ### Internal 140 | - Support VS Code package split 141 | - Use `vscode-ext-decoration` package 142 | 143 | ## [6.2.1] - 2019-05-27 144 | ### Fixed 145 | - Security Alert: tar 146 | 147 | ## [6.2.0] - 2019-03-25 148 | ### Added 149 | - Improvements to README, describing commands and shortcuts (issue [#52](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/52)) 150 | - Improvements to README, describing shortcut conflicts for MacOS users (issue [#40](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/40)) 151 | 152 | ### Fixed 153 | - Selection issue when cutting text (issue [#48](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/48)) 154 | 155 | ## [6.1.1] - 2019-03-15 156 | ### Fixed 157 | - What's New page broken in VS Code 1.32 due to CSS API changes 158 | 159 | ## [6.1.0] - 2018-12-17 160 | ### Added 161 | - New settings to choose gutter icon colors (icon fill and number) (Thanks to @vasilev-alex [PR #45](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/45)) 162 | 163 | ## [6.0.0] - 2018-11-26 164 | ### Added 165 | - What's New 166 | 167 | ## [5.2.0] - 2018-09-14 168 | ### Added 169 | - New Setting to choose background color of bookmarked lines (Thanks to @ibraimgm [PR #44](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/44)) 170 | - New Version Numbering based on `semver` 171 | 172 | ## [0.12.0 - 5.1.0] - 2018-09-14 173 | ### Added 174 | - Patreon button 175 | 176 | ## [0.11.1 - 5.0.1] - 2018-03-09 177 | ### Fixed 178 | - Error activating extension without workspace (folder) open (issue [#35](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/35)) 179 | 180 | ## [0.11.0 - 5.0.0] - 2017-11-13 181 | ### Added 182 | - Multi-root support (issue [#30](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/30)) 183 | 184 | ## [0.10.0 - 4.0.0] - 2017-05-27 185 | ### Changed 186 | - **TypeScript** and **VS Code engine** updated 187 | - Source code moved to `src` folder 188 | - Enabled **TSLint** 189 | - Source code organization 190 | 191 | ### Fixed 192 | - Error opening files outside the project in `List from All Files` (issue [#26](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/26)) 193 | - `List from All Files` command not working since VS Code 1.12 (issue [#25](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/25)) 194 | 195 | ## [0.9.1 - 3.1.1] - 2017-05-11 196 | ### Fixed 197 | - Bookmarks disapearing/incorrectly moving when new lines are added above (issue [#23](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/23)) 198 | 199 | ## [0.9.0 - 3.1.0] - 2017-04-23 200 | ### Added 201 | - New Setting to choose how bookmarks _Navigate Through All Files_ (issue [#6](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/6)) 202 | 203 | ## [0.8.0 - 3.0.0] - 2017-04-02 204 | ### Added 205 | - New Setting to allow Bookmarks to be saved in the project (inside `.vscode` folder) 206 | 207 | ### Changed 208 | - Bookmarks are now _always_ Sticky 209 | 210 | ## [0.7.0 - 2.4.0] - 2017-03-06 211 | ### Added 212 | - Avoid unnecessary scrolling (issue [#18](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/18)) 213 | 214 | ## [0.6.0 - 2.3.0] - 2017-01-03 215 | ### Added 216 | - Toggle Bookmark 0 and Jump to Bookmark 0 (PR [#16](https://github.com/alefragnani/vscode-numbered-bookmarks/pull/16) - kudos to @DeegC) 217 | 218 | ## [0.5.2 - 2.2.2] - 2016-12-03 219 | ### Added 220 | - Tags added for Marketplace presentation 221 | 222 | ## [0.5.1 - 2.2.1] - 2016-12-03 223 | ### Fixed 224 | - Bookmarks becomes invalid when documents are modified outside VSCode 225 | 226 | ## [0.5.0 - 2.2.0] - 2016-09-26 227 | ### Added 228 | - New Command `List from all files` 229 | - New Command `Clear from all files` 230 | 231 | ## [0.4.2 - 2.1.2] - 2016-09-19 232 | ### Fixed 233 | - Bookmarks missing in _Insider release 1.6.0_ (issue [#11](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/11)) 234 | 235 | ## [0.4.1 - 2.1.1] - 2016-09-03 236 | ### Fixed 237 | - Remove extension activation log (issue [#10](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/10)) 238 | 239 | ## [0.4.0 - 2.1.0] - 2016-06-17 240 | ### Added 241 | - New Setting to Sticky Bookmarks 242 | 243 | ## [0.3.0 - 2.0.0] - 2016-03-08 244 | ### Added 245 | - Bookmarks are also rendered in the overview ruler 246 | 247 | ### Fixed 248 | - Incompatibility with **Code February Release** 0.10.10 (issue [#4](https://github.com/alefragnani/vscode-numbered-bookmarks/issues/4)) 249 | 250 | ## [0.2.0 - 1.0.0] - 2016-02-15 251 | ### Added 252 | - New Command `List` 253 | 254 | ## [0.1.0 - 0.9.0] - 2016-02-06 255 | 256 | * Initial release 257 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "numbered-bookmarks", 3 | "displayName": "Numbered Bookmarks", 4 | "description": "Mark lines and jump to them, in Delphi style", 5 | "version": "9.0.0", 6 | "publisher": "alefragnani", 7 | "engines": { 8 | "vscode": "^1.73.0" 9 | }, 10 | "extensionKind": [ 11 | "ui", 12 | "workspace" 13 | ], 14 | "capabilities": { 15 | "virtualWorkspaces": true, 16 | "untrustedWorkspaces": { 17 | "supported": true 18 | } 19 | }, 20 | "categories": [ 21 | "Other" 22 | ], 23 | "keywords": [ 24 | "bookmark", 25 | "sticky", 26 | "jump", 27 | "mark", 28 | "navigation", 29 | "Delphi", 30 | "multi-root ready" 31 | ], 32 | "icon": "images/icon.png", 33 | "license": "GPL-3.0", 34 | "homepage": "https://github.com/alefragnani/vscode-numbered-bookmarks/blob/master/README.md", 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/alefragnani/vscode-numbered-bookmarks.git" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/alefragnani/vscode-numbered-bookmarks/issues" 41 | }, 42 | "sponsor": { 43 | "url": "https://github.com/sponsors/alefragnani" 44 | }, 45 | "activationEvents": [ 46 | "onStartupFinished" 47 | ], 48 | "main": "./dist/extension-node.js", 49 | "l10n": "./l10n", 50 | "contributes": { 51 | "commands": [ 52 | { 53 | "command": "numberedBookmarks.toggleBookmark0", 54 | "title": "%numberedBookmarks.commands.toggleBookmark0.title%", 55 | "category": "Numbered Bookmarks" 56 | }, 57 | { 58 | "command": "numberedBookmarks.toggleBookmark1", 59 | "title": "%numberedBookmarks.commands.toggleBookmark1.title%", 60 | "category": "Numbered Bookmarks" 61 | }, 62 | { 63 | "command": "numberedBookmarks.toggleBookmark2", 64 | "title": "%numberedBookmarks.commands.toggleBookmark2.title%", 65 | "category": "Numbered Bookmarks" 66 | }, 67 | { 68 | "command": "numberedBookmarks.toggleBookmark3", 69 | "title": "%numberedBookmarks.commands.toggleBookmark3.title%", 70 | "category": "Numbered Bookmarks" 71 | }, 72 | { 73 | "command": "numberedBookmarks.toggleBookmark4", 74 | "title": "%numberedBookmarks.commands.toggleBookmark4.title%", 75 | "category": "Numbered Bookmarks" 76 | }, 77 | { 78 | "command": "numberedBookmarks.toggleBookmark5", 79 | "title": "%numberedBookmarks.commands.toggleBookmark5.title%", 80 | "category": "Numbered Bookmarks" 81 | }, 82 | { 83 | "command": "numberedBookmarks.toggleBookmark6", 84 | "title": "%numberedBookmarks.commands.toggleBookmark6.title%", 85 | "category": "Numbered Bookmarks" 86 | }, 87 | { 88 | "command": "numberedBookmarks.toggleBookmark7", 89 | "title": "%numberedBookmarks.commands.toggleBookmark7.title%", 90 | "category": "Numbered Bookmarks" 91 | }, 92 | { 93 | "command": "numberedBookmarks.toggleBookmark8", 94 | "title": "%numberedBookmarks.commands.toggleBookmark8.title%", 95 | "category": "Numbered Bookmarks" 96 | }, 97 | { 98 | "command": "numberedBookmarks.toggleBookmark9", 99 | "title": "%numberedBookmarks.commands.toggleBookmark9.title%", 100 | "category": "Numbered Bookmarks" 101 | }, 102 | { 103 | "command": "numberedBookmarks.jumpToBookmark0", 104 | "title": "%numberedBookmarks.commands.jumpToBookmark0.title%", 105 | "category": "Numbered Bookmarks" 106 | }, 107 | { 108 | "command": "numberedBookmarks.jumpToBookmark1", 109 | "title": "%numberedBookmarks.commands.jumpToBookmark1.title%", 110 | "category": "Numbered Bookmarks" 111 | }, 112 | { 113 | "command": "numberedBookmarks.jumpToBookmark2", 114 | "title": "%numberedBookmarks.commands.jumpToBookmark2.title%", 115 | "category": "Numbered Bookmarks" 116 | }, 117 | { 118 | "command": "numberedBookmarks.jumpToBookmark3", 119 | "title": "%numberedBookmarks.commands.jumpToBookmark3.title%", 120 | "category": "Numbered Bookmarks" 121 | }, 122 | { 123 | "command": "numberedBookmarks.jumpToBookmark4", 124 | "title": "%numberedBookmarks.commands.jumpToBookmark4.title%", 125 | "category": "Numbered Bookmarks" 126 | }, 127 | { 128 | "command": "numberedBookmarks.jumpToBookmark5", 129 | "title": "%numberedBookmarks.commands.jumpToBookmark5.title%", 130 | "category": "Numbered Bookmarks" 131 | }, 132 | { 133 | "command": "numberedBookmarks.jumpToBookmark6", 134 | "title": "%numberedBookmarks.commands.jumpToBookmark6.title%", 135 | "category": "Numbered Bookmarks" 136 | }, 137 | { 138 | "command": "numberedBookmarks.jumpToBookmark7", 139 | "title": "%numberedBookmarks.commands.jumpToBookmark7.title%", 140 | "category": "Numbered Bookmarks" 141 | }, 142 | { 143 | "command": "numberedBookmarks.jumpToBookmark8", 144 | "title": "%numberedBookmarks.commands.jumpToBookmark8.title%", 145 | "category": "Numbered Bookmarks" 146 | }, 147 | { 148 | "command": "numberedBookmarks.jumpToBookmark9", 149 | "title": "%numberedBookmarks.commands.jumpToBookmark9.title%", 150 | "category": "Numbered Bookmarks" 151 | }, 152 | { 153 | "command": "numberedBookmarks.list", 154 | "title": "%numberedBookmarks.commands.list.title%", 155 | "category": "Numbered Bookmarks" 156 | }, 157 | { 158 | "command": "numberedBookmarks.listFromAllFiles", 159 | "title": "%numberedBookmarks.commands.listFromAllFiles.title%", 160 | "category": "Numbered Bookmarks" 161 | }, 162 | { 163 | "command": "numberedBookmarks.clear", 164 | "title": "%numberedBookmarks.commands.clear.title%", 165 | "category": "Numbered Bookmarks" 166 | }, 167 | { 168 | "command": "numberedBookmarks.clearFromAllFiles", 169 | "title": "%numberedBookmarks.commands.clearFromAllFiles.title%", 170 | "category": "Numbered Bookmarks" 171 | }, 172 | { 173 | "command": "numberedBookmarks.whatsNew", 174 | "title": "%numberedBookmarks.commands.whatsNew.title%", 175 | "category": "Numbered Bookmarks" 176 | }, 177 | { 178 | "command": "numberedBookmarks.whatsNewContextMenu", 179 | "title": "%numberedBookmarks.commands.whatsNewContextMenu.title%" 180 | } 181 | ], 182 | "submenus": [ 183 | { 184 | "id": "numberedBookmarks.context.toggle", 185 | "label": "%numberedBookmarks.context.toggle.label%" 186 | }, 187 | { 188 | "id": "numberedBookmarks.context.jump", 189 | "label": "%numberedBookmarks.context.jump.label%" 190 | } 191 | ], 192 | "configuration": { 193 | "type": "object", 194 | "title": "%numberedBookmarks.configuration.title%", 195 | "properties": { 196 | "numberedBookmarks.saveBookmarksInProject": { 197 | "type": "boolean", 198 | "default": false, 199 | "description": "%numberedBookmarks.configuration.saveBookmarksInProject.description%" 200 | }, 201 | "numberedBookmarks.experimental.enableNewStickyEngine": { 202 | "type": "boolean", 203 | "default": true, 204 | "description": "%numberedBookmarks.configuration.experimental.enableNewStickyEngine.description%" 205 | }, 206 | "numberedBookmarks.keepBookmarksOnLineDelete": { 207 | "type": "boolean", 208 | "default": false, 209 | "description": "%numberedBookmarks.configuration.keepBookmarksOnLineDelete.description%" 210 | }, 211 | "numberedBookmarks.showBookmarkNotDefinedWarning": { 212 | "type": "boolean", 213 | "default": false, 214 | "description": "%numberedBookmarks.configuration.showBookmarkNotDefinedWarning.description%" 215 | }, 216 | "numberedBookmarks.navigateThroughAllFiles": { 217 | "type": "string", 218 | "default": "false", 219 | "description": "%numberedBookmarks.configuration.navigateThroughAllFiles.description%", 220 | "enum": [ 221 | "false", 222 | "replace", 223 | "allowDuplicates" 224 | ] 225 | }, 226 | "numberedBookmarks.gutterIconFillColor": { 227 | "type": "string", 228 | "default": "#00ff25", 229 | "description": "%numberedBookmarks.configuration.gutterIconFillColor.description%" 230 | }, 231 | "numberedBookmarks.gutterIconNumberColor": { 232 | "type": "string", 233 | "default": "#000", 234 | "description": "%numberedBookmarks.configuration.gutterIconNumberColor.description%" 235 | }, 236 | "numberedBookmarks.revealLocation": { 237 | "type": "string", 238 | "default": "center", 239 | "description": "%numberedBookmarks.configuration.revealLocation.description%", 240 | "enum": [ 241 | "top", 242 | "center" 243 | ], 244 | "enumDescriptions": [ 245 | "%numberedBookmarks.configuration.revealLocation.enumDescriptions.top%", 246 | "%numberedBookmarks.configuration.revealLocation.enumDescriptions.center%" 247 | ] 248 | } 249 | } 250 | }, 251 | "keybindings": [ 252 | { 253 | "command": "numberedBookmarks.toggleBookmark1", 254 | "key": "ctrl+shift+1", 255 | "mac": "cmd+shift+1", 256 | "when": "editorTextFocus" 257 | }, 258 | { 259 | "command": "numberedBookmarks.toggleBookmark2", 260 | "key": "ctrl+shift+2", 261 | "mac": "cmd+shift+2", 262 | "when": "editorTextFocus" 263 | }, 264 | { 265 | "command": "numberedBookmarks.toggleBookmark3", 266 | "key": "ctrl+shift+3", 267 | "mac": "cmd+shift+3", 268 | "when": "editorTextFocus" 269 | }, 270 | { 271 | "command": "numberedBookmarks.toggleBookmark4", 272 | "key": "ctrl+shift+4", 273 | "mac": "cmd+shift+4", 274 | "when": "editorTextFocus" 275 | }, 276 | { 277 | "command": "numberedBookmarks.toggleBookmark5", 278 | "key": "ctrl+shift+5", 279 | "mac": "cmd+shift+5", 280 | "when": "editorTextFocus" 281 | }, 282 | { 283 | "command": "numberedBookmarks.toggleBookmark6", 284 | "key": "ctrl+shift+6", 285 | "mac": "cmd+shift+6", 286 | "when": "editorTextFocus" 287 | }, 288 | { 289 | "command": "numberedBookmarks.toggleBookmark7", 290 | "key": "ctrl+shift+7", 291 | "mac": "cmd+shift+7", 292 | "when": "editorTextFocus" 293 | }, 294 | { 295 | "command": "numberedBookmarks.toggleBookmark8", 296 | "key": "ctrl+shift+8", 297 | "mac": "cmd+shift+8", 298 | "when": "editorTextFocus" 299 | }, 300 | { 301 | "command": "numberedBookmarks.toggleBookmark9", 302 | "key": "ctrl+shift+9", 303 | "mac": "cmd+shift+9", 304 | "when": "editorTextFocus" 305 | }, 306 | { 307 | "command": "numberedBookmarks.jumpToBookmark1", 308 | "key": "ctrl+1", 309 | "mac": "cmd+1", 310 | "when": "editorTextFocus" 311 | }, 312 | { 313 | "command": "numberedBookmarks.jumpToBookmark2", 314 | "key": "ctrl+2", 315 | "mac": "cmd+2", 316 | "when": "editorTextFocus" 317 | }, 318 | { 319 | "command": "numberedBookmarks.jumpToBookmark3", 320 | "key": "ctrl+3", 321 | "mac": "cmd+3", 322 | "when": "editorTextFocus" 323 | }, 324 | { 325 | "command": "numberedBookmarks.jumpToBookmark4", 326 | "key": "ctrl+4", 327 | "mac": "cmd+4", 328 | "when": "editorTextFocus" 329 | }, 330 | { 331 | "command": "numberedBookmarks.jumpToBookmark5", 332 | "key": "ctrl+5", 333 | "mac": "cmd+5", 334 | "when": "editorTextFocus" 335 | }, 336 | { 337 | "command": "numberedBookmarks.jumpToBookmark6", 338 | "key": "ctrl+6", 339 | "mac": "cmd+6", 340 | "when": "editorTextFocus" 341 | }, 342 | { 343 | "command": "numberedBookmarks.jumpToBookmark7", 344 | "key": "ctrl+7", 345 | "mac": "cmd+7", 346 | "when": "editorTextFocus" 347 | }, 348 | { 349 | "command": "numberedBookmarks.jumpToBookmark8", 350 | "key": "ctrl+8", 351 | "mac": "cmd+8", 352 | "when": "editorTextFocus" 353 | }, 354 | { 355 | "command": "numberedBookmarks.jumpToBookmark9", 356 | "key": "ctrl+9", 357 | "mac": "cmd+9", 358 | "when": "editorTextFocus" 359 | } 360 | ], 361 | "colors": [ 362 | { 363 | "id": "numberedBookmarks.lineBackground", 364 | "description": "%numberedBookmarks.colors.lineBackground.description%", 365 | "defaults": { 366 | "dark": "#00000000", 367 | "light": "#00000000", 368 | "highContrast": "#00000000" 369 | } 370 | }, 371 | { 372 | "id": "numberedBookmarks.lineBorder", 373 | "description": "%numberedBookmarks.colors.lineBorder.description%", 374 | "defaults": { 375 | "dark": "#00000000", 376 | "light": "#00000000", 377 | "highContrast": "#00000000" 378 | } 379 | }, 380 | { 381 | "id": "numberedBookmarks.overviewRuler", 382 | "description": "%numberedBookmarks.colors.overviewRuler.description%", 383 | "defaults": { 384 | "dark": "#12ff1288", 385 | "light": "#12ff1288", 386 | "highContrast": "#12ff1288" 387 | } 388 | } 389 | ], 390 | "menus": { 391 | "commandPalette": [ 392 | { 393 | "command": "numberedBookmarks.whatsNewContextMenu", 394 | "when": "false" 395 | } 396 | ], 397 | "extension/context": [ 398 | { 399 | "command": "numberedBookmarks.whatsNewContextMenu", 400 | "group": "numberedBookmarks", 401 | "when": "extension == alefragnani.numbered-bookmarks && extensionStatus==installed" 402 | } 403 | ], 404 | "editor/context": [ 405 | { 406 | "submenu": "numberedBookmarks.context.toggle", 407 | "group": "numberedBookmarks@1" 408 | }, 409 | { 410 | "submenu": "numberedBookmarks.context.jump", 411 | "group": "numberedBookmarks@2" 412 | } 413 | ], 414 | "numberedBookmarks.context.toggle": [ 415 | { 416 | "command": "numberedBookmarks.toggleBookmark1", 417 | "group": "numberedBookmarks" 418 | }, 419 | { 420 | "command": "numberedBookmarks.toggleBookmark2", 421 | "group": "numberedBookmarks" 422 | }, 423 | { 424 | "command": "numberedBookmarks.toggleBookmark3", 425 | "group": "numberedBookmarks" 426 | }, 427 | { 428 | "command": "numberedBookmarks.toggleBookmark4", 429 | "group": "numberedBookmarks" 430 | }, 431 | { 432 | "command": "numberedBookmarks.toggleBookmark5", 433 | "group": "numberedBookmarks" 434 | }, 435 | { 436 | "command": "numberedBookmarks.toggleBookmark6", 437 | "group": "numberedBookmarks" 438 | }, 439 | { 440 | "command": "numberedBookmarks.toggleBookmark7", 441 | "group": "numberedBookmarks" 442 | }, 443 | { 444 | "command": "numberedBookmarks.toggleBookmark8", 445 | "group": "numberedBookmarks" 446 | }, 447 | { 448 | "command": "numberedBookmarks.toggleBookmark9", 449 | "group": "numberedBookmarks" 450 | }, 451 | { 452 | "command": "numberedBookmarks.toggleBookmark0", 453 | "group": "numberedBookmarks@10" 454 | } 455 | ], 456 | "numberedBookmarks.context.jump": [ 457 | { 458 | "command": "numberedBookmarks.jumpToBookmark1", 459 | "group": "numberedBookmarks" 460 | }, 461 | { 462 | "command": "numberedBookmarks.jumpToBookmark2", 463 | "group": "numberedBookmarks" 464 | }, 465 | { 466 | "command": "numberedBookmarks.jumpToBookmark3", 467 | "group": "numberedBookmarks" 468 | }, 469 | { 470 | "command": "numberedBookmarks.jumpToBookmark4", 471 | "group": "numberedBookmarks" 472 | }, 473 | { 474 | "command": "numberedBookmarks.jumpToBookmark5", 475 | "group": "numberedBookmarks" 476 | }, 477 | { 478 | "command": "numberedBookmarks.jumpToBookmark6", 479 | "group": "numberedBookmarks" 480 | }, 481 | { 482 | "command": "numberedBookmarks.jumpToBookmark7", 483 | "group": "numberedBookmarks" 484 | }, 485 | { 486 | "command": "numberedBookmarks.jumpToBookmark8", 487 | "group": "numberedBookmarks" 488 | }, 489 | { 490 | "command": "numberedBookmarks.jumpToBookmark9", 491 | "group": "numberedBookmarks" 492 | }, 493 | { 494 | "command": "numberedBookmarks.jumpToBookmark0", 495 | "group": "numberedBookmarks@10" 496 | } 497 | ] 498 | }, 499 | "walkthroughs": [ 500 | { 501 | "id": "numberedBookmarksWelcome", 502 | "title": "%numberedBookmarks.walkthroughs.title%", 503 | "description": "%numberedBookmarks.walkthroughs.description%", 504 | "steps": [ 505 | { 506 | "id": "toggle", 507 | "title": "%numberedBookmarks.walkthroughs.toggle.title%", 508 | "description": "%numberedBookmarks.walkthroughs.toggle.description%", 509 | "media": { 510 | "markdown": "walkthrough/toggle.md" 511 | } 512 | }, 513 | { 514 | "id": "navigateToNumberedBookmarks", 515 | "title": "%numberedBookmarks.walkthroughs.navigateToNumberedBookmarks.title%", 516 | "description": "%numberedBookmarks.walkthroughs.navigateToNumberedBookmarks.description%", 517 | "media": { 518 | "markdown": "walkthrough/navigateToNumberedBookmarks.md" 519 | } 520 | }, 521 | { 522 | "id": "inspiredInDelphiButOpenToOthers", 523 | "title": "%numberedBookmarks.walkthroughs.inspiredInDelphiButOpenToOthers.title%", 524 | "description": "%numberedBookmarks.walkthroughs.inspiredInDelphiButOpenToOthers.description%", 525 | "media": { 526 | "markdown": "walkthrough/inspiredInDelphiButOpenToOthers.md" 527 | } 528 | }, 529 | { 530 | "id": "workingWithRemotes", 531 | "title": "%numberedBookmarks.walkthroughs.workingWithRemotes.title%", 532 | "description": "%numberedBookmarks.walkthroughs.workingWithRemotes.description%", 533 | "media": { 534 | "markdown": "walkthrough/workingWithRemotes.md" 535 | } 536 | }, 537 | { 538 | "id": "customizingAppearance", 539 | "title": "%numberedBookmarks.walkthroughs.customizingAppearance.title%", 540 | "description": "%numberedBookmarks.walkthroughs.customizingAppearance.description%", 541 | "media": { 542 | "markdown": "walkthrough/customizingAppearance.md" 543 | } 544 | } 545 | ] 546 | } 547 | ] 548 | }, 549 | "eslintConfig": { 550 | "extends": [ 551 | "vscode-ext" 552 | ] 553 | }, 554 | "scripts": { 555 | "build": "webpack --mode development", 556 | "watch": "webpack --watch --mode development", 557 | "vscode:prepublish": "webpack --mode production", 558 | "webpack": "webpack --mode development", 559 | "webpack-dev": "webpack --mode development --watch", 560 | "compile": "tsc -p ./", 561 | "lint": "eslint -c package.json --ext .ts src vscode-whats-new", 562 | "pretest": "npm run compile && npm run lint", 563 | "test-compile": "tsc -p ./ && npm run webpack", 564 | "just-test": "node ./out/src/test/runTest.js", 565 | "test": "npm run test-compile && npm run just-test" 566 | }, 567 | "dependencies": { 568 | "os-browserify": "^0.3.0", 569 | "path-browserify": "^1.0.1", 570 | "vscode-ext-codicons": "^1.1.0", 571 | "vscode-ext-decoration": "1.1.0" 572 | }, 573 | "devDependencies": { 574 | "@types/glob": "^8.1.0", 575 | "@types/mocha": "^10.0.0", 576 | "@types/node": "^18.17.0", 577 | "@types/vscode": "^1.73.0", 578 | "@typescript-eslint/eslint-plugin": "^6.0.0", 579 | "@typescript-eslint/parser": "^6.0.0", 580 | "@vscode/test-electron": "^2.5.2", 581 | "eslint": "^8.1.0", 582 | "eslint-config-vscode-ext": "^1.1.0", 583 | "mocha": "^11.1.0", 584 | "terser-webpack-plugin": "^5.2.4", 585 | "ts-loader": "^9.2.5", 586 | "typescript": "^5.3.3", 587 | "webpack": "^5.94.0", 588 | "webpack-cli": "^4.8.0" 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Alessandro Fragnani. All rights reserved. 3 | * Licensed under the GPLv3 License. See License.md in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import { l10n, Position, TextDocument, Uri } from "vscode"; 8 | 9 | import { Bookmark, BookmarkQuickPickItem } from "./core/bookmark"; 10 | import { NO_BOOKMARK_DEFINED } from "./core/constants"; 11 | import { Controller } from "./core/controller"; 12 | import { clearBookmarks, hasBookmarks, indexOfBookmark, isBookmarkDefined, listBookmarks } from "./core/operations"; 13 | import { revealPosition, previewPositionInDocument, revealPositionInDocument } from "./utils/reveal"; 14 | import { Sticky } from "./sticky/stickyLegacy"; 15 | import { loadBookmarks, saveBookmarks } from "./storage/workspaceState"; 16 | import { Container } from "./core/container"; 17 | import { registerWhatsNew } from "./whats-new/commands"; 18 | import { codicons } from "vscode-ext-codicons"; 19 | import { appendPath, getRelativePath, parsePosition } from "./utils/fs"; 20 | import { File } from "./core/file"; 21 | import { updateDecorationsInActiveEditor, createBookmarkDecorations, TextEditorDecorationTypePair } from "./decoration/decoration"; 22 | import { pickController } from "./quickpick/controllerPicker"; 23 | import { updateStickyBookmarks } from "./sticky/sticky"; 24 | 25 | export async function activate(context: vscode.ExtensionContext) { 26 | 27 | Container.context = context; 28 | 29 | await registerWhatsNew(); 30 | 31 | let activeController: Controller; 32 | let controllers: Controller[] = []; 33 | let activeEditorCountLine: number; 34 | let timeout = null; 35 | let activeEditor = vscode.window.activeTextEditor; 36 | let activeFile: File; 37 | 38 | let bookmarkDecorationTypePairs = createBookmarkDecorations(); 39 | bookmarkDecorationTypePairs.forEach(decorator => { 40 | context.subscriptions.push(decorator.gutterDecoration); 41 | context.subscriptions.push(decorator.lineDecoration); 42 | }); 43 | 44 | // load pre-saved bookmarks 45 | await loadWorkspaceState(); 46 | 47 | // Connect it to the Editors Events 48 | if (activeEditor) { 49 | getActiveController(activeEditor.document); 50 | activeController.addFile(activeEditor.document.uri); 51 | activeEditorCountLine = activeEditor.document.lineCount; 52 | activeFile = activeController.fromUri(activeEditor.document.uri); 53 | triggerUpdateDecorations(); 54 | } 55 | 56 | // new docs 57 | // vscode.workspace.onDidOpenTextDocument(doc => { 58 | // // activeEditorCountLine = doc.lineCount; 59 | // getActiveController(doc); 60 | // activeController.addFile(doc.uri); 61 | // }); 62 | 63 | vscode.window.onDidChangeActiveTextEditor(editor => { 64 | activeEditor = editor; 65 | if (editor) { 66 | activeEditorCountLine = editor.document.lineCount; 67 | getActiveController(editor.document); 68 | activeController.addFile(editor.document.uri); 69 | activeFile = activeController.fromUri(editor.document.uri); 70 | 71 | triggerUpdateDecorations(); 72 | } 73 | }, null, context.subscriptions); 74 | 75 | vscode.workspace.onDidChangeTextDocument(event => { 76 | if (activeEditor && event.document === activeEditor.document) { 77 | let updatedBookmark = true; 78 | // call sticky function when the activeEditor is changed 79 | if (activeFile && activeFile.bookmarks.length > 0) { 80 | if (vscode.workspace.getConfiguration("numberedBookmarks").get("experimental.enableNewStickyEngine", true)) { 81 | updatedBookmark = updateStickyBookmarks(event, activeFile, 82 | activeEditor, activeController); 83 | } else { 84 | updatedBookmark = Sticky.stickyBookmarks(event, activeEditorCountLine, 85 | activeFile, activeEditor); 86 | } 87 | } 88 | 89 | activeEditorCountLine = event.document.lineCount; 90 | updateDecorations(); 91 | 92 | if (updatedBookmark) { 93 | saveWorkspaceState(); 94 | } 95 | } 96 | }, null, context.subscriptions); 97 | 98 | vscode.workspace.onDidChangeConfiguration(async event => { 99 | if (event.affectsConfiguration("numberedBookmarks.gutterIconFillColor") 100 | || event.affectsConfiguration("numberedBookmarks.gutterIconNumberColor") 101 | ) { 102 | if (bookmarkDecorationTypePairs.length > 0) { 103 | bookmarkDecorationTypePairs.forEach(decorator => { 104 | decorator.gutterDecoration.dispose(); 105 | decorator.lineDecoration.dispose(); 106 | }); 107 | } 108 | bookmarkDecorationTypePairs = createBookmarkDecorations(); 109 | bookmarkDecorationTypePairs.forEach(decorator => { 110 | context.subscriptions.push(decorator.gutterDecoration); 111 | context.subscriptions.push(decorator.lineDecoration); 112 | }); 113 | // context.subscriptions.push(...bookmarkDecorationTypePairs[0], ...bookmarkDecorationTypePairs[1]); 114 | } 115 | }, null, context.subscriptions); 116 | 117 | vscode.workspace.onDidRenameFiles(async rename => { 118 | 119 | if (rename.files.length === 0) { return; } 120 | 121 | for (const file of rename.files) { 122 | const files = activeController.files.map(file => file.path); 123 | const stat = await vscode.workspace.fs.stat(file.newUri); 124 | 125 | const fileRelativeOldPath = getRelativePath(activeController.workspaceFolder.uri.path, file.oldUri.path); 126 | const fileRelativeNewPath = getRelativePath(activeController.workspaceFolder.uri.path, file.newUri.path); 127 | 128 | if (stat.type === vscode.FileType.File) { 129 | if (files.includes(fileRelativeOldPath)) { 130 | activeController.updateFilePath(fileRelativeOldPath, fileRelativeNewPath); 131 | } 132 | } 133 | if (stat.type === vscode.FileType.Directory) { 134 | activeController.updateDirectoryPath(fileRelativeOldPath, fileRelativeNewPath); 135 | } 136 | } 137 | 138 | saveWorkspaceState(); 139 | if (activeEditor) { 140 | activeFile = activeController.fromUri(activeEditor.document.uri); 141 | updateDecorations(); 142 | } 143 | }, null, context.subscriptions); 144 | 145 | // Timeout 146 | function triggerUpdateDecorations() { 147 | if (timeout) { 148 | clearTimeout(timeout); 149 | } 150 | timeout = setTimeout(updateDecorations, 100); 151 | } 152 | 153 | function getDecorationPair(n: number): TextEditorDecorationTypePair { 154 | return bookmarkDecorationTypePairs[ n ]; 155 | } 156 | 157 | // Evaluate (prepare the list) and DRAW 158 | function updateDecorations() { 159 | updateDecorationsInActiveEditor(activeEditor, activeFile, getDecorationPair); 160 | } 161 | 162 | // other commands 163 | for (let i = 0; i <= 9; i++) { 164 | vscode.commands.registerCommand( 165 | `numberedBookmarks.toggleBookmark${i}`, 166 | () => toggleBookmark(i, vscode.window.activeTextEditor.selection.active) 167 | ); 168 | vscode.commands.registerCommand( 169 | `numberedBookmarks.jumpToBookmark${i}`, 170 | () => jumpToBookmark(i) 171 | ); 172 | } 173 | 174 | vscode.commands.registerCommand("numberedBookmarks.clear", () => { 175 | clearBookmarks(activeFile); 176 | 177 | saveWorkspaceState(); 178 | updateDecorations(); 179 | }); 180 | 181 | vscode.commands.registerCommand("numberedBookmarks.clearFromAllFiles", async () => { 182 | 183 | const controller = await pickController(controllers, activeController); 184 | if (!controller) { 185 | return 186 | } 187 | 188 | for (const file of controller.files) { 189 | clearBookmarks(file); 190 | } 191 | 192 | saveWorkspaceState(); 193 | updateDecorations(); 194 | }); 195 | 196 | vscode.commands.registerCommand("numberedBookmarks.list", () => { 197 | // no bookmark 198 | if (!hasBookmarks(activeFile)) { 199 | vscode.window.showInformationMessage(l10n.t("No Bookmarks found")); 200 | return; 201 | } 202 | 203 | // push the items 204 | const items: vscode.QuickPickItem[] = []; 205 | for (const bookmark of activeFile.bookmarks) { 206 | if (isBookmarkDefined(bookmark)) { 207 | const bookmarkLine = bookmark.line + 1; 208 | const bookmarkColumn = bookmark.column + 1; 209 | const lineText = vscode.window.activeTextEditor.document.lineAt(bookmarkLine - 1).text.trim(); 210 | items.push({ 211 | label: lineText, 212 | description: "(Ln " + bookmarkLine.toString() + ", Col " + 213 | bookmarkColumn.toString() + ")" 214 | }); 215 | } 216 | } 217 | 218 | // pick one 219 | const currentPosition: Position = vscode.window.activeTextEditor.selection.active; 220 | const options = { 221 | placeHolder: l10n.t("Type a line number or a piece of code to navigate to"), 222 | matchOnDescription: true, 223 | matchOnDetail: true, 224 | onDidSelectItem: item => { 225 | const itemT = item; 226 | const point: Bookmark = parsePosition(itemT.description); 227 | if (point) { 228 | revealPosition(point.line - 1, point.column - 1); 229 | } 230 | } 231 | }; 232 | 233 | vscode.window.showQuickPick(items, options).then(selection => { 234 | if (typeof selection === "undefined") { 235 | revealPosition(currentPosition.line, currentPosition.character); 236 | return; 237 | } 238 | const itemT = selection; 239 | const point: Bookmark = parsePosition(itemT.description); 240 | if (point) { 241 | revealPosition(point.line - 1, point.column - 1); 242 | } 243 | }); 244 | }); 245 | 246 | vscode.commands.registerCommand("numberedBookmarks.listFromAllFiles", async () => { 247 | 248 | const controller = await pickController(controllers, activeController); 249 | if (!controller) { 250 | return 251 | } 252 | 253 | // no bookmark 254 | let someFileHasBookmark: boolean; 255 | for (const file of controller.files) { 256 | someFileHasBookmark = someFileHasBookmark || hasBookmarks(file); 257 | if (someFileHasBookmark) break; 258 | } 259 | if (!someFileHasBookmark) { 260 | vscode.window.showInformationMessage(l10n.t("No Bookmarks found")); 261 | return; 262 | } 263 | 264 | // push the items 265 | const items: BookmarkQuickPickItem[] = []; 266 | const activeTextEditor = vscode.window.activeTextEditor; 267 | const promisses = []; 268 | const currentPosition: Position = vscode.window.activeTextEditor?.selection.active; 269 | 270 | // tslint:disable-next-line:prefer-for-of 271 | for (let index = 0; index < controller.files.length; index++) { 272 | const file = controller.files[ index ]; 273 | const pp = listBookmarks(file, controller.workspaceFolder); 274 | promisses.push(pp); 275 | } 276 | 277 | Promise.all(promisses).then( 278 | (values) => { 279 | // tslint:disable-next-line:prefer-for-of 280 | for (let index = 0; index < values.length; index++) { 281 | const element = values[ index ]; 282 | // tslint:disable-next-line:prefer-for-of 283 | for (let indexInside = 0; indexInside < element.length; indexInside++) { 284 | const elementInside = element[ indexInside ]; 285 | 286 | if (activeTextEditor && 287 | elementInside.detail.toString().toLocaleLowerCase() === getRelativePath(controller.workspaceFolder?.uri?.path, activeTextEditor.document.uri.path).toLocaleLowerCase()) { 288 | items.push( 289 | { 290 | label: elementInside.label, 291 | description: elementInside.description, 292 | uri: elementInside.uri 293 | } 294 | ); 295 | } else { 296 | items.push( 297 | { 298 | label: elementInside.label, 299 | description: elementInside.description, 300 | detail: elementInside.detail, 301 | uri: elementInside.uri 302 | } 303 | ); 304 | } 305 | } 306 | 307 | } 308 | 309 | // sort 310 | // - active document 311 | // - no octicon - document inside project 312 | // - with octicon - document outside project 313 | const itemsSorted: BookmarkQuickPickItem[] = items.sort(function(a: BookmarkQuickPickItem, b: BookmarkQuickPickItem): number { 314 | if (!a.detail && !b.detail) { 315 | return 0; 316 | } 317 | 318 | if (!a.detail && b.detail) { 319 | return -1; 320 | } 321 | 322 | if (a.detail && !b.detail) { 323 | return 1; 324 | } 325 | 326 | if ((a.detail.toString().indexOf(codicons.file_submodule + " ") === 0) && (b.detail.toString().indexOf(codicons.file_directory + " ") === 0)) { 327 | return -1; 328 | } 329 | 330 | if ((a.detail.toString().indexOf(codicons.file_directory + " ") === 0) && (b.detail.toString().indexOf(codicons.file_submodule + " ") === 0)) { 331 | return 1; 332 | } 333 | 334 | if ((a.detail.toString().indexOf(codicons.file_submodule + " ") === 0) && (b.detail.toString().indexOf(codicons.file_submodule + " ") === -1)) { 335 | return 1; 336 | } 337 | 338 | if ((a.detail.toString().indexOf(codicons.file_submodule + " ") === -1) && (b.detail.toString().indexOf(codicons.file_submodule + " ") === 0)) { 339 | return -1; 340 | } 341 | 342 | if ((a.detail.toString().indexOf(codicons.file_directory + " ") === 0) && (b.detail.toString().indexOf(codicons.file_directory + " ") === -1)) { 343 | return 1; 344 | } 345 | 346 | if ((a.detail.toString().indexOf(codicons.file_directory + " ") === -1) && (b.detail.toString().indexOf(codicons.file_directory + " ") === 0)) { 347 | return -1; 348 | } 349 | 350 | return 0; 351 | }); 352 | 353 | const options = { 354 | placeHolder: l10n.t("Type a line number or a piece of code to navigate to"), 355 | matchOnDescription: true, 356 | onDidSelectItem: item => { 357 | 358 | const itemT = item 359 | 360 | let fileUri: Uri; 361 | if (!itemT.detail) { 362 | fileUri = activeTextEditor.document.uri; 363 | } else { 364 | fileUri = itemT.uri; 365 | } 366 | 367 | const point: Bookmark = parsePosition(itemT.description); 368 | if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri.fsPath.toLowerCase() === fileUri.fsPath.toLowerCase()) { 369 | revealPosition(point.line - 1, point.column - 1); 370 | } else { 371 | previewPositionInDocument(point, fileUri); 372 | } 373 | } 374 | }; 375 | vscode.window.showQuickPick(itemsSorted, options).then(async selection => { 376 | if (typeof selection === "undefined") { 377 | if (!activeTextEditor) { 378 | vscode.commands.executeCommand("workbench.action.closeActiveEditor"); 379 | return; 380 | } else { 381 | vscode.workspace.openTextDocument(activeTextEditor.document.uri).then(doc => { 382 | vscode.window.showTextDocument(doc).then(() => { 383 | revealPosition(currentPosition.line, currentPosition.character) 384 | return; 385 | }); 386 | }); 387 | } 388 | } 389 | 390 | if (typeof selection === "undefined") { 391 | return; 392 | } 393 | 394 | const point: Bookmark = parsePosition(selection.description); 395 | if (!selection.detail) { 396 | if (point) { 397 | revealPosition(point.line - 1, point.column - 1); 398 | } 399 | } 400 | }); 401 | } 402 | ); 403 | }); 404 | 405 | function getActiveController(document: TextDocument): void { 406 | // system files don't have workspace, so use the first one [0] 407 | if (!vscode.workspace.getWorkspaceFolder(document.uri)) { 408 | activeController = controllers[0]; 409 | return; 410 | } 411 | 412 | if (controllers.length > 1) { 413 | activeController = controllers.find(ctrl => 414 | ctrl.workspaceFolder.uri.path === vscode.workspace.getWorkspaceFolder(document.uri).uri.path); 415 | } 416 | } 417 | 418 | async function loadWorkspaceState(): Promise { 419 | 420 | // no workspace, load as `undefined` and will always be from `workspaceState` 421 | if (!vscode.workspace.workspaceFolders) { 422 | const ctrl = await loadBookmarks(undefined); 423 | controllers.push(ctrl); 424 | activeController = ctrl; 425 | return; 426 | } 427 | 428 | // NOT `saveBookmarksInProject` 429 | if (!vscode.workspace.getConfiguration("numberedBookmarks").get("saveBookmarksInProject", false)) { 430 | //if (vscode.workspace.workspaceFolders.length > 1) { 431 | // no matter how many workspaceFolders exists, will always load from [0] because even with 432 | // multi-root, there would be no way to load state from different folders 433 | const ctrl = await loadBookmarks(vscode.workspace.workspaceFolders[0]); 434 | controllers.push(ctrl); 435 | activeController = ctrl; 436 | return; 437 | } 438 | 439 | // `saveBookmarksInProject` TRUE 440 | // single or multi-root, will load from each `workspaceFolder` 441 | controllers = await Promise.all( 442 | vscode.workspace.workspaceFolders!.map(async workspaceFolder => { 443 | const ctrl = await loadBookmarks(workspaceFolder); 444 | return ctrl; 445 | }) 446 | ); 447 | if (controllers.length === 1) { 448 | activeController = controllers[0]; 449 | } 450 | } 451 | 452 | function saveWorkspaceState(): void { 453 | // no workspace, there is only one `controller`, and will always be from `workspaceState` 454 | if (!vscode.workspace.workspaceFolders) { 455 | saveBookmarks(activeController); 456 | return; 457 | } 458 | 459 | // NOT `saveBookmarksInProject`, will load from `workspaceFolders[0]` - as before 460 | if (!vscode.workspace.getConfiguration("numberedBookmarks").get("saveBookmarksInProject", false)) { 461 | // no matter how many workspaceFolders exists, will always save to [0] because even with 462 | // multi-root, there would be no way to save state to different folders 463 | saveBookmarks(activeController); 464 | return; 465 | } 466 | 467 | // `saveBookmarksInProject` TRUE 468 | // single or multi-root, will save to each `workspaceFolder` 469 | controllers.forEach(controller => { 470 | saveBookmarks(controller); 471 | }); 472 | } 473 | 474 | function toggleBookmark(n: number, position: vscode.Position) { 475 | // fix issue emptyAtLaunch 476 | if (!activeFile) { 477 | activeController.addFile(vscode.window.activeTextEditor.document.uri); 478 | activeFile = activeController.fromUri(vscode.window.activeTextEditor.document.uri); 479 | } 480 | 481 | // there is another bookmark already set for this line? 482 | const index: number = indexOfBookmark(activeFile, position.line); 483 | if (index >= 0) { 484 | clearBookmark(index); 485 | } 486 | 487 | // if was myself, then I want to 'remove' 488 | if (index !== n) { 489 | activeFile.bookmarks[ n ] = { 490 | line: position.line, 491 | column: position.character 492 | } 493 | 494 | // when _toggling_ only "replace" differs, because it has to _invalidate_ that bookmark from other files 495 | const navigateThroughAllFiles: string = vscode.workspace.getConfiguration("numberedBookmarks").get("navigateThroughAllFiles", "false"); 496 | if (navigateThroughAllFiles === "replace") { 497 | for (const element of activeController.files) { 498 | if (element.path !== activeFile.path) { 499 | element.bookmarks[ n ] = NO_BOOKMARK_DEFINED; 500 | } 501 | } 502 | } 503 | } 504 | 505 | saveWorkspaceState(); 506 | updateDecorations(); 507 | } 508 | 509 | function clearBookmark(n: number) { 510 | activeFile.bookmarks[ n ] = NO_BOOKMARK_DEFINED; 511 | } 512 | 513 | async function jumpToBookmark(n: number) { 514 | if (!activeFile) { 515 | return; 516 | } 517 | 518 | // when _jumping_ each config has its own behavior 519 | const navigateThroughAllFiles: string = vscode.workspace.getConfiguration("numberedBookmarks").get("navigateThroughAllFiles", "false"); 520 | switch (navigateThroughAllFiles) { 521 | case "replace": 522 | // is it already set? 523 | if (activeFile.bookmarks[ n ].line < 0) { 524 | 525 | // no, look for another document that contains that bookmark 526 | // I can start from the first because _there is only one_ 527 | for (const element of activeController.files) { 528 | if ((element.path !== activeFile.path) && (isBookmarkDefined(element.bookmarks[ n ]))) { 529 | await revealPositionInDocument(element.bookmarks[n], activeController.getFileUri(element)); 530 | return; 531 | } 532 | } 533 | } else { 534 | revealPosition(activeFile.bookmarks[ n ].line, activeFile.bookmarks[ n ].column); 535 | } 536 | 537 | break; 538 | 539 | case "allowDuplicates": { 540 | 541 | // this file has, and I'm not in the line 542 | if ((isBookmarkDefined(activeFile.bookmarks[ n ])) && 543 | (activeFile.bookmarks[ n ].line !== vscode.window.activeTextEditor.selection.active.line)) { 544 | revealPosition(activeFile.bookmarks[ n ].line, activeFile.bookmarks[ n ].column); 545 | break; 546 | } 547 | 548 | // no, look for another document that contains that bookmark 549 | // I CAN'T start from the first because _there can be duplicates_ 550 | const currentFile: number = activeController.indexFromPath(activeFile.path); 551 | let found = false; 552 | 553 | // to the end 554 | for (let index = currentFile; index < activeController.files.length; index++) { 555 | const element = activeController.files[ index ]; 556 | if ((!found) && (element.path !== activeFile.path) && (isBookmarkDefined(element.bookmarks[ n ]))) { 557 | found = true; 558 | const uriDocument = appendPath(activeController.workspaceFolder.uri, element.path); 559 | await revealPositionInDocument(element.bookmarks[n], uriDocument); 560 | return; 561 | } 562 | } 563 | 564 | if (!found) { 565 | for (let index = 0; index < currentFile; index++) { 566 | const element = activeController.files[ index ]; 567 | if ((!found) && (element.path !== activeFile.path) && (isBookmarkDefined(element.bookmarks[ n ]))) { 568 | found = true; 569 | const uriDocument = appendPath(activeController.workspaceFolder.uri, element.path); 570 | await revealPositionInDocument(element.bookmarks[n], uriDocument); 571 | return; 572 | } 573 | } 574 | 575 | if (!found) { 576 | if (vscode.workspace.getConfiguration("numberedBookmarks").get("showBookmarkNotDefinedWarning", false)) { 577 | vscode.window.showWarningMessage(l10n.t("The Bookmark {0} is not defined", n)); 578 | } 579 | return; 580 | } 581 | } 582 | 583 | break; 584 | } 585 | 586 | default: // "false" 587 | // is it already set? 588 | if (activeFile.bookmarks.length === 0) { 589 | vscode.window.showInformationMessage(l10n.t("No Bookmarks found")); 590 | return; 591 | } 592 | 593 | if (activeFile.bookmarks[ n ].line < 0) { 594 | if (vscode.workspace.getConfiguration("numberedBookmarks").get("showBookmarkNotDefinedWarning", false)) { 595 | vscode.window.showWarningMessage(l10n.t("The Bookmark {0} is not defined", n)); 596 | } 597 | return; 598 | } 599 | 600 | revealPosition(activeFile.bookmarks[ n ].line, activeFile.bookmarks[ n ].column); 601 | 602 | break; 603 | } 604 | } 605 | 606 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . --------------------------------------------------------------------------------