├── .gitignore
├── images
├── icon.png
├── screenshot.png
└── octicons
│ ├── light
│ ├── pencil.svg
│ ├── book.svg
│ └── list-unordered.svg
│ └── dark
│ ├── pencil.svg
│ ├── book.svg
│ └── list-unordered.svg
├── .vscodeignore
├── .editorconfig
├── tsconfig.json
├── .vscode
├── settings.json
├── launch.json
└── tasks.json
├── README.md
├── package.json
└── src
└── extension.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | *.vsix
4 | vsc-extension-quickstart.md
5 |
--------------------------------------------------------------------------------
/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spywhere/vscode-mark-jump/HEAD/images/icon.png
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spywhere/vscode-mark-jump/HEAD/images/screenshot.png
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | typings/**
3 | out/test/**
4 | test/**
5 | src/**
6 | **/*.map
7 | .gitignore
8 | tsconfig.json
9 | vsc-extension-quickstart.md
10 | *.vsix
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": ["es6"],
7 | "sourceMap": true
8 | },
9 | "exclude": [
10 | "node_modules",
11 | ".vscode-test"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.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 // set this to false to include "out" folder in search results
8 | },
9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
10 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.1.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 | "sourceMaps": true,
13 | "outDir": "${workspaceRoot}/out",
14 | "preLaunchTask": "npm"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/images/octicons/light/pencil.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/octicons/dark/pencil.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/images/octicons/light/book.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/octicons/dark/book.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 |
9 | // A task runner that calls a custom npm script that compiles the extension.
10 | {
11 | "version": "0.1.0",
12 |
13 | // we want to run npm
14 | "command": "npm",
15 |
16 | // the command is a shell script
17 | "isShellCommand": true,
18 |
19 | // show the output window only if unrecognized errors occur.
20 | "showOutput": "silent",
21 |
22 | // we run the custom script "compile" as defined in package.json
23 | "args": ["run", "compile", "--loglevel", "silent"],
24 |
25 | // The tsc compiler is started in watching mode
26 | "isWatching": true,
27 |
28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output.
29 | "problemMatcher": "$tsc-watch"
30 | }
--------------------------------------------------------------------------------
/images/octicons/light/list-unordered.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/octicons/dark/list-unordered.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Mark Jump
2 | [](https://marketplace.visualstudio.com/items?itemName=spywhere.mark-jump)
3 | [](https://marketplace.visualstudio.com/items?itemName=spywhere.mark-jump)
4 |
5 | Jump to the marked section in the code
6 |
7 | 
8 |
9 | ### What is Mark Jump?
10 | Mark Jump is simply an extension that let you jump across your marking points in the code.
11 |
12 | ### How to use it?
13 | Simply install the extension, Mark Jump should show the mark count in the status bar!
14 | Use arrow keys to jump between them, click to jump to it.
15 |
16 | You can also use the following key bindings to jump through various type of marks...
17 |
18 | - `Ctrl+Alt+P` / `Ctrl+Cmd+P` (`markJump.jumpToProjectMarks`): Jump to all marks in the project
19 | - `Ctrl+Alt+M` / `Ctrl+Cmd+M` (`markJump.jumpToMarks`): Jump to all marks (either in current editor or a whole project)
20 | - `Ctrl+Alt+S` / `Ctrl+Cmd+S` (`markJump.jumpToEditorMarks.section`): Jump to all section marks (in current editor)
21 | - `Ctrl+Alt+T` / `Ctrl+Cmd+T` (`markJump.jumpToEditorMarks.todo`): Jump to all TODOs (in current editor)
22 | - `Ctrl+Alt+N` / `Ctrl+Cmd+N` (`markJump.jumpToEditorMarks.note`): Jump to all Notes (in current editor)
23 |
24 | - `Ctrl+Alt+,` / `Ctrl+Cmd+,` (`markJump.jumpToPreviousMark`): Jump to previous mark (in current editor)
25 | - `Ctrl+Alt+.` / `Ctrl+Cmd+.` (`markJump.jumpToNextMark`): Jump to next mark (in current editor)
26 |
27 | ### How to write the marks?
28 | Just simply write one of these syntax in your code, Mark Jump will found it right away!
29 |
30 | You can also add more syntax via the configurations (pull requests are welcomed).
31 |
32 | #### Section Marks
33 | - `// MARK: Section name`
34 | - `# pragma Section name` (case sensitive)
35 |
36 | #### TODOs
37 | - `// TODO: Text goes here`
38 | - `// TODO(writer name): Text goes here`
39 |
40 | #### Notes
41 | - `// NOTE: Text goes here`
42 | - `// NOTE(writer name): Text goes here`
43 |
44 | #### Mark Filter
45 | You can custom how "Jump to previous/next mark" jump by use a specific command instead of general one.
46 |
47 | - To filter only "Section" mark, add `.section` after a command name.
48 | - To filter only "To Do" mark, add `.todo` after a command name.
49 | - To filter only "Note" mark, add `.note` after a command name.
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mark-jump",
3 | "displayName": "Mark Jump",
4 | "description": "Jump to the marked section in the code",
5 | "version": "0.9.0",
6 | "publisher": "spywhere",
7 | "icon": "images/icon.png",
8 | "bugs": {
9 | "url": "https://github.com/spywhere/vscode-mark-jump/issues"
10 | },
11 | "homepage": "https://github.com/spywhere/vscode-mark-jump/blob/master/README.md",
12 | "keywords": [
13 | "marker",
14 | "section",
15 | "jump",
16 | "comment"
17 | ],
18 | "license": "MIT",
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/spywhere/vscode-mark-jump.git"
22 | },
23 | "engines": {
24 | "vscode": "^1.17.0"
25 | },
26 | "categories": [
27 | "Other"
28 | ],
29 | "activationEvents": [
30 | "*"
31 | ],
32 | "main": "./out/extension",
33 | "contributes": {
34 | "views": {
35 | "explorer": [
36 | {
37 | "id": "markJump",
38 | "name": "Mark Jump",
39 | "when": "config.markJump.showExplorerView == true"
40 | }
41 | ]
42 | },
43 | "commands": [
44 | {
45 | "command": "markJump.jumpToProjectMarks",
46 | "title": "Mark Jump: Jump to Project-wide Marks..."
47 | },
48 | {
49 | "command": "markJump.jumpToMarks",
50 | "title": "Mark Jump: Jump to Marks..."
51 | },
52 | {
53 | "command": "markJump.jumpToEditorMarks.section",
54 | "title": "Mark Jump: Jump to Sections..."
55 | },
56 | {
57 | "command": "markJump.jumpToEditorMarks.todo",
58 | "title": "Mark Jump: Jump to TODOs..."
59 | },
60 | {
61 | "command": "markJump.jumpToEditorMarks.note",
62 | "title": "Mark Jump: Jump to Notes..."
63 | }
64 | ],
65 | "keybindings": [
66 | {
67 | "command": "markJump.jumpToProjectMarks",
68 | "key": "ctrl+alt+p",
69 | "mac": "ctrl+cmd+p"
70 | },
71 | {
72 | "command": "markJump.jumpToMarks",
73 | "key": "ctrl+alt+m",
74 | "mac": "ctrl+cmd+m"
75 | },
76 | {
77 | "command": "markJump.jumpToEditorMarks.section",
78 | "when": "editorTextFocus",
79 | "key": "ctrl+alt+s",
80 | "mac": "ctrl+cmd+s"
81 | },
82 | {
83 | "command": "markJump.jumpToEditorMarks.todo",
84 | "when": "editorTextFocus",
85 | "key": "ctrl+alt+t",
86 | "mac": "ctrl+cmd+t"
87 | },
88 | {
89 | "command": "markJump.jumpToEditorMarks.note",
90 | "when": "editorTextFocus",
91 | "key": "ctrl+alt+n",
92 | "mac": "ctrl+cmd+n"
93 | },
94 | {
95 | "command": "markJump.jumpToPreviousMark",
96 | "when": "editorTextFocus",
97 | "key": "ctrl+alt+,",
98 | "mac": "ctrl+cmd+,"
99 | },
100 | {
101 | "command": "markJump.jumpToNextMark",
102 | "when": "editorTextFocus",
103 | "key": "ctrl+alt+.",
104 | "mac": "ctrl+cmd+."
105 | }
106 | ],
107 | "configuration": {
108 | "type": "object",
109 | "title": "Mark Jump Configurations",
110 | "properties": {
111 | "markJump.alwaysOpenDocument": {
112 | "type": "boolean",
113 | "default": true,
114 | "description": "Automatically open and show a document according to the marks.",
115 | "scope": "window"
116 | },
117 | "markJump.headingSymbol": {
118 | "type": "string",
119 | "default": "└─",
120 | "description": "Heading symbol to indicate multiple level of marks.",
121 | "scope": "window"
122 | },
123 | "markJump.testPatterns": {
124 | "type": "array",
125 | "default": [
126 | "//\\s*(\\>+)?\\s*[Mm][Aa][Rr][Kk]",
127 | "#\\s*(\\>+)?\\s*pragma",
128 | "//\\s*(\\>+)?\\s*[Tt][Oo][Dd][Oo]",
129 | "//\\s*(\\>+)?\\s*[Ff][Ii][Xx][Mm][Ee]",
130 | "//\\s*(\\>+)?\\s*[Nn][Oo][Tt][Ee]"
131 | ],
132 | "items": {
133 | "type": "string"
134 | },
135 | "description": "A list of regular patterns to test the file (when match, the whole file will be inspected).",
136 | "scope": "window"
137 | },
138 | "markJump.additionalTestPatterns": {
139 | "type": "array",
140 | "default": [
141 | "//\\s*(\\>+)?\\s*[Mm][Aa][Rr][Kk]",
142 | "#\\s*(\\>+)?\\s*pragma",
143 | "//\\s*(\\>+)?\\s*[Tt][Oo][Dd][Oo]",
144 | "//\\s*(\\>+)?\\s*[Ff][Ii][Xx][Mm][Ee]",
145 | "//\\s*(\\>+)?\\s*[Nn][Oo][Tt][Ee]"
146 | ],
147 | "items": {
148 | "type": "string"
149 | },
150 | "description": "Additional list of regular patterns to test the file. Use this configuration to avoid replacing default patterns.",
151 | "scope": "window"
152 | },
153 | "markJump.sectionPatterns": {
154 | "type": "array",
155 | "default": [
156 | "//\\s*(?\\>+)?\\s*[Mm][Aa][Rr][Kk]\\s*:\\s*(?.+)$",
157 | "#\\s*(?\\>+)?\\s*pragma\\s+(?.+)$"
158 | ],
159 | "items": {
160 | "type": "string"
161 | },
162 | "description": "A list of regular patterns to match the section.",
163 | "scope": "window"
164 | },
165 | "markJump.additionalSectionPatterns": {
166 | "type": "array",
167 | "default": [],
168 | "items": {
169 | "type": "string"
170 | },
171 | "description": "Additional list of regular pattern to match the section. Use this configuration to avoid replacing default patterns.",
172 | "scope": "window"
173 | },
174 | "markJump.todoPatterns": {
175 | "type": "array",
176 | "default": [
177 | "//\\s*(?\\>+)?\\s*[Tt][Oo][Dd][Oo]\\s*(\\((?[^\\)]+)\\))?\\s*:\\s*(?.+)$",
178 | "//\\s*(?\\>+)?\\s*[Ff][Ii][Xx][Mm][Ee]\\s*(\\((?[^\\)]+)\\))?\\s*:\\s*(?.+)$"
179 | ],
180 | "items": {
181 | "type": "string"
182 | },
183 | "description": "A list of regular pattern to match the TODOs.",
184 | "scope": "window"
185 | },
186 | "markJump.additionalTODOPatterns": {
187 | "type": "array",
188 | "default": [],
189 | "items": {
190 | "type": "string"
191 | },
192 | "description": "Additional list of regular pattern to match the TODOs. Use this configuration to avoid replacing default patterns.",
193 | "scope": "window"
194 | },
195 | "markJump.notePatterns": {
196 | "type": "array",
197 | "default": [
198 | "//\\s*(?\\>+)?\\s*[Nn][Oo][Tt][Ee]\\s*(\\((?[^\\)]+)\\))?\\s*:\\s*(?.+)$"
199 | ],
200 | "items": {
201 | "type": "string"
202 | },
203 | "description": "A list of regular pattern to match the notes.",
204 | "scope": "window"
205 | },
206 | "markJump.additionalNotePatterns": {
207 | "type": "array",
208 | "default": [],
209 | "items": {
210 | "type": "string"
211 | },
212 | "description": "Additional list of regular pattern to match the notes. Use this configuration to avoid replacing default patterns.",
213 | "scope": "window"
214 | },
215 | "markJump.showProjectMarks": {
216 | "type": "boolean",
217 | "default": true,
218 | "description": "Show project-wide marks on the status bar.",
219 | "scope": "window"
220 | },
221 | "markJump.includeFilePattern": {
222 | "type": "string",
223 | "default": "**/*",
224 | "description": "Glob pattern for files to be included in project marks.",
225 | "scope": "window"
226 | },
227 | "markJump.excludeFilePattern": {
228 | "type": "string",
229 | "default": "**/node_modules/**",
230 | "description": "Glob pattern for files to be excluded in project marks.",
231 | "scope": "window"
232 | },
233 | "markJump.highlightColor.dark": {
234 | "type": "string",
235 | "default": "rgba(60, 60, 60, 0.75)",
236 | "description": "Highlight rendering color for dark themes.",
237 | "scope": "window"
238 | },
239 | "markJump.highlightColor.light": {
240 | "type": "string",
241 | "default": "rgba(220, 220, 220, 0.75)",
242 | "description": "Highlight rendering color for light themes.",
243 | "scope": "window"
244 | },
245 | "markJump.showStatusItem": {
246 | "type": "boolean",
247 | "default": true,
248 | "description": "Show numbers of marks in the status bar.",
249 | "scope": "window"
250 | },
251 | "markJump.showExplorerView": {
252 | "type": "boolean",
253 | "default": true,
254 | "description": "Show marks in the explorer viewlet.",
255 | "scope": "window"
256 | },
257 | "markJump.maximumLimit": {
258 | "type": "number",
259 | "default": 30,
260 | "description": "Show warning when a number of matched files exceed this limit. Set to -1 for unlimited.",
261 | "scope": "window"
262 | },
263 | "markJump.strictLimit": {
264 | "type": "string",
265 | "default": "none",
266 | "description": "Keep the maximum limit as is.\n- \"none\" to show warnings and suggestions\n- \"disable\" to disable\n- \"limit\" to limit to the specified limit.",
267 | "enum": [
268 | "none",
269 | "disable",
270 | "limit"
271 | ],
272 | "scope": "window"
273 | }
274 | }
275 | }
276 | },
277 | "scripts": {
278 | "vscode:prepublish": "tsc -p ./",
279 | "compile": "tsc -watch -p ./",
280 | "postinstall": "node ./node_modules/vscode/bin/install"
281 | },
282 | "devDependencies": {
283 | "@types/node": "^8.0.53",
284 | "@types/xregexp": "^3.0.29",
285 | "typescript": "^2.6.1",
286 | "vscode": "^1.1.36"
287 | },
288 | "dependencies": {
289 | "xregexp": "^3.2.0"
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 | import * as vscode from "vscode";
3 | import * as XRegExp from "xregexp";
4 | import * as fs from "fs";
5 | import * as path from "path";
6 |
7 | export function activate(context: vscode.ExtensionContext) {
8 | let markJump = new MarkJump();
9 | let treeProvider = new MarkJumpTreeProvider(markJump, context);
10 | context.subscriptions.push(markJump);
11 | context.subscriptions.push(vscode.window.registerTreeDataProvider(
12 | "markJump", treeProvider
13 | ));
14 | context.subscriptions.push(new MarkJumpController(markJump, treeProvider));
15 | }
16 |
17 | interface ActionItem extends vscode.MessageItem {
18 | danger?: {
19 | title: string;
20 | action: string;
21 | cancel: string;
22 | };
23 | action?: () => (Thenable | number);
24 | }
25 |
26 | class MarkJumpController {
27 | private markJump: MarkJump;
28 | private treeProvider: MarkJumpTreeProvider;
29 | private disposable: vscode.Disposable;
30 | private lastLine: number = undefined;
31 |
32 | constructor(markJump: MarkJump, treeProvider: MarkJumpTreeProvider){
33 | this.markJump = markJump;
34 | this.treeProvider = treeProvider;
35 |
36 | let subscriptions: vscode.Disposable[] = [];
37 | subscriptions.push(vscode.commands.registerCommand(
38 | "markJump.jumpToProjectMarks", () => {
39 | this.markJump.jumpToEditorMark(
40 | undefined, undefined, "section", "todo", "note"
41 | );
42 | }
43 | ));
44 | subscriptions.push(vscode.commands.registerCommand(
45 | "markJump.jumpToMarks", () => {
46 | this.markJump.jumpToMark(true, "section", "todo", "note");
47 | }
48 | ));
49 | subscriptions.push(vscode.commands.registerCommand(
50 | "markJump.jumpToEditorMarks", () => {
51 | this.markJump.jumpToMark(false, "section", "todo", "note");
52 | }
53 | ));
54 | subscriptions.push(vscode.commands.registerCommand(
55 | "markJump.jumpToPreviousMark", () => {
56 | this.markJump.jumpToPreviousMark("section", "todo", "note");
57 | }
58 | ));
59 | subscriptions.push(vscode.commands.registerCommand(
60 | "markJump.jumpToNextMark", () => {
61 | this.markJump.jumpToNextMark("section", "todo", "note");
62 | }
63 | ));
64 | ["section", "todo", "note"].forEach((type) => {
65 | subscriptions.push(vscode.commands.registerCommand(
66 | `markJump.jumpToProjectMarks.${ type }`, () => {
67 | this.markJump.jumpToEditorMark(
68 | undefined, undefined, type
69 | );
70 | }
71 | ));
72 | subscriptions.push(vscode.commands.registerCommand(
73 | `markJump.jumpToMarks.${ type }`, () => {
74 | this.markJump.jumpToMark(true, type);
75 | }
76 | ));
77 | subscriptions.push(vscode.commands.registerCommand(
78 | `markJump.jumpToEditorMarks.${ type }`, () => {
79 | this.markJump.jumpToMark(false, type);
80 | }
81 | ));
82 | subscriptions.push(vscode.commands.registerCommand(
83 | `markJump.jumpToPreviousMark.${ type }`, () => {
84 | this.markJump.jumpToPreviousMark(type);
85 | }
86 | ));
87 | subscriptions.push(vscode.commands.registerCommand(
88 | `markJump.jumpToNextMark.${ type }`, () => {
89 | this.markJump.jumpToNextMark(type);
90 | }
91 | ));
92 | });
93 | subscriptions.push(vscode.commands.registerCommand(
94 | "markJump.revealMark", (mark: BaseMarkItem) => {
95 | this.markJump.openAndRevealMark(mark);
96 | }
97 | ));
98 | this.markJump.createStatusBar();
99 | vscode.workspace.onDidOpenTextDocument(document => {
100 | this.markJump.updateStatusBar(false);
101 | this.treeProvider.refresh();
102 | }, this, subscriptions);
103 | vscode.workspace.onDidCloseTextDocument(document => {
104 | this.lastLine = undefined;
105 | this.markJump.updateStatusBar();
106 | this.treeProvider.refresh();
107 | }, this, subscriptions);
108 | vscode.workspace.onDidChangeConfiguration(() => {
109 | this.markJump.updateStatusBar();
110 | this.treeProvider.refresh();
111 | }, this, subscriptions);
112 | vscode.window.onDidChangeTextEditorViewColumn(event => {
113 | this.lastLine = undefined;
114 | this.markJump.updateStatusBar();
115 | this.treeProvider.refresh();
116 | }, this, subscriptions);
117 | vscode.window.onDidChangeActiveTextEditor(editor => {
118 | this.lastLine = undefined;
119 | this.markJump.updateStatusBar();
120 | this.treeProvider.refresh();
121 | }, this, subscriptions);
122 | vscode.window.onDidChangeTextEditorSelection(event => {
123 | if(event.selections.length > 1){
124 | return;
125 | }
126 | if(this.lastLine === event.selections[0].active.line){
127 | return;
128 | }
129 | this.lastLine = event.selections[0].active.line;
130 | this.markJump.updateStatusBar();
131 | this.treeProvider.refresh();
132 | }, this, subscriptions);
133 |
134 | this.disposable = vscode.Disposable.from(...subscriptions);
135 | }
136 |
137 | dispose(){
138 | this.disposable.dispose();
139 | }
140 | }
141 |
142 | class MarkJumpTreeProvider implements vscode.TreeDataProvider {
143 | private markJump: MarkJump;
144 | private context: vscode.ExtensionContext;
145 | private _onDidChangeTreeData = new vscode.EventEmitter();
146 | readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
147 |
148 | constructor(markJump: MarkJump, context: vscode.ExtensionContext){
149 | this.markJump = markJump;
150 | this.context = context;
151 | }
152 |
153 | refresh() {
154 | this._onDidChangeTreeData.fire();
155 | }
156 |
157 | getTreeItem(element: vscode.TreeItem){
158 | return element;
159 | }
160 |
161 | getChildren(element?: vscode.TreeItem) {
162 | if (!this.markJump.treeAllow) {
163 | return [];
164 | }
165 | return this.markJump.getMarks(
166 | vscode.window.activeTextEditor, undefined, true
167 | ).then(marks => {
168 | return marks.map(mark => {
169 | let type = "";
170 | if (mark.type === "note") {
171 | type = "book";
172 | } else if (mark.type === "todo") {
173 | type = "pencil";
174 | } else if (mark.type === "section") {
175 | type = "list-unordered";
176 | }
177 | return {
178 | command: {
179 | title: "Reveal",
180 | command: "markJump.revealMark",
181 | arguments: [mark]
182 | },
183 | label: `${ mark.description }`,
184 | iconPath: {
185 | light: this.context.asAbsolutePath(
186 | `./images/octicons/light/${ type }.svg`
187 | ),
188 | dark: this.context.asAbsolutePath(
189 | `./images/octicons/dark/${ type }.svg`
190 | )
191 | }
192 | }
193 | });
194 | });
195 | }
196 | }
197 |
198 | interface BaseMarkItem {
199 | range: vscode.Range;
200 | uri: vscode.Uri;
201 | }
202 |
203 | interface MarkQuickPickItem extends vscode.QuickPickItem, BaseMarkItem {}
204 |
205 | interface MarkItem extends BaseMarkItem {
206 | type: "section" | "todo" | "note";
207 | heading?: string;
208 | writer?: string;
209 | description: string;
210 | lineNumber: number;
211 | }
212 |
213 | interface MarkFilter {
214 | test(lineText: string): boolean;
215 | getItem(
216 | uri: vscode.Uri,
217 | lineNumber: number,
218 | lineText: string
219 | ): MarkItem | undefined;
220 | }
221 |
222 | class MarkJump {
223 | treeAllow = true;
224 | lastSelections: vscode.Selection[];
225 | statusItem: vscode.StatusBarItem;
226 | useLimit = -1;
227 | testPatterns = [];
228 | private lastWarning: number = Date.now() - 10000;
229 |
230 | createStatusBar(){
231 | if(this.statusItem){
232 | return;
233 | }
234 | this.statusItem = vscode.window.createStatusBarItem(
235 | vscode.StatusBarAlignment.Left, 10
236 | );
237 | this.statusItem.command = "markJump.jumpToMarks";
238 | this.statusItem.hide();
239 | this.updateStatusBar();
240 | }
241 |
242 | dispose(){
243 | this.statusItem.dispose();
244 | }
245 |
246 | updateStatusBar(withProjectWide: boolean = true){
247 | let configurations = vscode.workspace.getConfiguration("markJump");
248 | this.treeAllow = configurations.get("showExplorerView");
249 | if (!configurations.get("showStatusItem")) {
250 | this.statusItem.hide();
251 | return;
252 | }
253 |
254 | let editor = vscode.window.activeTextEditor;
255 | let marks = this.getMarks(
256 | editor, undefined, withProjectWide
257 | ).then(marks => {
258 | if(marks.length <= 0){
259 | this.statusItem.hide();
260 | return;
261 | }
262 |
263 | let markCount = {
264 | section: 0,
265 | todo: 0,
266 | note: 0
267 | };
268 |
269 | marks.forEach(mark => {
270 | markCount[mark.type] += 1;
271 | });
272 |
273 | this.statusItem.text = `${
274 | markCount.section > 0 ?
275 | `$(list-unordered) ${markCount.section} ` : ""
276 | }${
277 | markCount.todo > 0 ?
278 | `$(pencil) ${markCount.todo} ` : ""
279 | }${
280 | markCount.note > 0 ?
281 | `$(book) ${markCount.note}` : ""
282 | }`;
283 |
284 | let tooltips: string[] = [];
285 | if(markCount.section > 0){
286 | tooltips.push(
287 | `${markCount.section} Section${
288 | markCount.section > 1 ? "s" : ""
289 | }`
290 | );
291 | }
292 | if(markCount.todo > 0){
293 | tooltips.push(
294 | `${markCount.todo} TODO${
295 | markCount.todo > 1 ? "s" : ""
296 | }`
297 | );
298 | }
299 | if(markCount.note > 0){
300 | tooltips.push(
301 | `${markCount.note} Note${
302 | markCount.note > 1 ? "s" : ""
303 | }`
304 | );
305 | }
306 |
307 | this.statusItem.tooltip = `${
308 | editor ? "" : "In this project: "
309 | }${
310 | tooltips.join(", ")
311 | }`;
312 |
313 | this.statusItem.show();
314 | });
315 | }
316 |
317 | jumpToPreviousMark(...filters: string[]){
318 | let editor = vscode.window.activeTextEditor;
319 | if (!editor) {
320 | return;
321 | }
322 | this.getMarks(editor, {
323 | offset: editor.selection.active.line - 1,
324 | limit: -1
325 | }, false, ...filters).then(marks => {
326 | if (marks.length > 0) {
327 | return Promise.resolve(marks);
328 | }
329 | return this.getMarks(editor, {
330 | offset: editor.document.lineCount - 1,
331 | limit: -1
332 | }, false, ...filters);
333 | }).then(marks => {
334 | if (marks.length <= 0) {
335 | return;
336 | }
337 | this.revealMark(editor, marks[0]);
338 | });
339 | }
340 |
341 | jumpToNextMark(...filters: string[]){
342 | let editor = vscode.window.activeTextEditor;
343 | if (!editor) {
344 | return;
345 | }
346 | this.getMarks(editor, {
347 | offset: editor.selection.active.line + 1,
348 | limit: 1
349 | }, false, ...filters).then(marks => {
350 | if (marks.length > 0) {
351 | return Promise.resolve(marks);
352 | }
353 | return this.getMarks(editor, {
354 | offset: 0,
355 | limit: 1
356 | }, false, ...filters);
357 | }).then(marks => {
358 | if (marks.length <= 0) {
359 | return;
360 | }
361 | this.revealMark(editor, marks[0]);
362 | });
363 | }
364 |
365 | jumpToMark(withProjectWide: boolean = true, ...filters: string[]){
366 | this.jumpToEditorMark(
367 | vscode.window.activeTextEditor, withProjectWide, ...filters
368 | );
369 | }
370 |
371 | buildHeading(length: number, headingSymbol: string){
372 | if (length <= 0 || !headingSymbol) {
373 | return "";
374 | }
375 | let firstSymbol = headingSymbol.substr(0, 1);
376 | let secondSymbol = headingSymbol.substr(1) || firstSymbol;
377 | let padding = " ";
378 | if (firstSymbol === " ") {
379 | padding = "";
380 | }
381 | return `${firstSymbol}${ secondSymbol.repeat(length - 1) }${padding}`;
382 | }
383 |
384 | jumpToEditorMark(
385 | editor?: vscode.TextEditor,
386 | withProjectWide: boolean = true,
387 | ...filters: string[]
388 | ){
389 | let configurations = vscode.workspace.getConfiguration("markJump");
390 | this.getMarks(editor, undefined, withProjectWide, ...filters)
391 | .then(marks => {
392 | if(marks.length <= 0){
393 | if(filters.length === 1 && filters.indexOf("todo") >= 0){
394 | vscode.window.showInformationMessage(
395 | "No TODO left. Well done!"
396 | );
397 | }else{
398 | vscode.window.showInformationMessage("No mark is set.");
399 | }
400 | return;
401 | }
402 |
403 | let options: vscode.DecorationRenderOptions = {
404 | isWholeLine: true
405 | };
406 |
407 | let darkValue = configurations.get(
408 | "highlightColor.dark"
409 | );
410 | let lightValue = configurations.get(
411 | "highlightColor.light"
412 | );
413 |
414 | if(darkValue){
415 | options.dark = {
416 | backgroundColor: darkValue,
417 | overviewRulerColor: darkValue
418 | };
419 | }
420 | if(lightValue){
421 | options.light = {
422 | backgroundColor: lightValue,
423 | overviewRulerColor: lightValue
424 | };
425 | }
426 |
427 | let headingSymbol = configurations.get("headingSymbol");
428 |
429 | if(editor){
430 | this.lastSelections = editor.selections;
431 | }
432 | let highlightDecoration = vscode.window.createTextEditorDecorationType(
433 | options
434 | );
435 | let lastEditor: vscode.TextEditor = undefined;
436 | vscode.window.showQuickPick(marks.map(mark => {
437 | let item: MarkQuickPickItem = {
438 | range: mark.range,
439 | uri: mark.uri,
440 | label: "",
441 | description: undefined
442 | };
443 |
444 | if(mark.type === "note"){
445 | item.label = `${
446 | this.buildHeading(
447 | (mark.heading || "").length, headingSymbol
448 | )
449 | }$(book) NOTE: ${mark.description}` || "";
450 |
451 | item.detail = (
452 | mark.writer ? `by ${mark.writer}` : undefined
453 | );
454 |
455 | item.description = `${
456 | editor ? "" : `${path.basename(mark.uri.fsPath)} `
457 | }on line ${mark.lineNumber + 1}`;
458 | }else if(mark.type === "todo"){
459 | item.label = `${
460 | this.buildHeading(
461 | (mark.heading || "").length, headingSymbol
462 | )
463 | }$(pencil) TODO: ${mark.description}` || "";
464 |
465 | item.detail = (
466 | mark.writer ? `by ${mark.writer}` : undefined
467 | );
468 |
469 | item.description = `${
470 | editor ? "" : `${path.basename(mark.uri.fsPath)} `
471 | }on line ${mark.lineNumber + 1}`;
472 | }else if(mark.type === "section"){
473 | item.label = `${
474 | this.buildHeading(
475 | (mark.heading || "").length, headingSymbol
476 | )
477 | }$(list-unordered) ${mark.description}` || "";
478 | item.description = `${
479 | editor ? "" : `${path.basename(mark.uri.fsPath)} `
480 | }on line ${mark.lineNumber + 1}`;
481 | }
482 |
483 | return item;
484 | }), {
485 | ignoreFocusOut: false,
486 | matchOnDescription: true,
487 | matchOnDetail: true,
488 | onDidSelectItem: (mark) => {
489 | if(!editor){
490 | if(!configurations.get("alwaysOpenDocument")){
491 | return;
492 | }
493 | vscode.workspace.openTextDocument(
494 | mark.uri
495 | ).then(document => {
496 | return vscode.window.showTextDocument(
497 | document, {
498 | preserveFocus: true,
499 | preview: true
500 | }
501 | );
502 | }).then(editor => {
503 | if(lastEditor){
504 | lastEditor.setDecorations(
505 | highlightDecoration, []
506 | );
507 | }
508 | editor.setDecorations(
509 | highlightDecoration, [mark.range]
510 | );
511 | this.revealMark(editor, mark);
512 | lastEditor = editor;
513 | });
514 | return;
515 | }
516 | editor.setDecorations(highlightDecoration, [mark.range]);
517 | this.revealMark(editor, mark);
518 | }
519 | }).then(mark => {
520 | if(lastEditor){
521 | lastEditor.setDecorations(
522 | highlightDecoration, []
523 | );
524 | }
525 | if(!editor){
526 | this.openAndRevealMark(mark);
527 | return;
528 | }
529 |
530 | this.revealMark(editor, mark);
531 | editor.setDecorations(highlightDecoration, []);
532 | highlightDecoration.dispose();
533 | });
534 | });
535 | }
536 |
537 | openAndRevealMark(mark: BaseMarkItem){
538 | let editor = vscode.window.visibleTextEditors.find((editor) => (
539 | editor.document.uri.toString() === mark.uri.toString()
540 | ));
541 | (
542 | editor ?
543 | Promise.resolve(editor.document) :
544 | vscode.workspace.openTextDocument(mark.uri)
545 | ).then((document) => vscode.window.showTextDocument(document, {
546 | preview: false
547 | })).then((editor) => this.revealMark(editor, mark));
548 | }
549 |
550 | revealMark(
551 | editor: vscode.TextEditor,
552 | mark?: BaseMarkItem
553 | ){
554 | if(!mark){
555 | editor.revealRange(
556 | this.lastSelections[0],
557 | vscode.TextEditorRevealType.InCenterIfOutsideViewport
558 | );
559 | editor.selections = this.lastSelections;
560 | return;
561 | }
562 | editor.revealRange(
563 | mark.range, vscode.TextEditorRevealType.InCenterIfOutsideViewport
564 | );
565 | editor.selection = new vscode.Selection(
566 | mark.range.end, mark.range.end
567 | );
568 | }
569 |
570 | getMarks(
571 | editor?: vscode.TextEditor,
572 | options?: {
573 | offset: number;
574 | limit: number;
575 | },
576 | withProjectWide: boolean = true,
577 | ...filterKeys: string[]
578 | ){
579 | return new Promise((resolve, reject) => {
580 | let configurations = vscode.workspace.getConfiguration("markJump");
581 | let filters: MarkFilter[] = [];
582 |
583 | if(filterKeys.length <= 0 || filterKeys.indexOf("section") >= 0){
584 | let patterns = configurations.get("sectionPatterns").concat(
585 | configurations.get("additionalSectionPatterns")
586 | );
587 | filters.push(new SectionFilter(patterns));
588 | }
589 | if(filterKeys.length <= 0 || filterKeys.indexOf("todo") >= 0){
590 | let patterns = configurations.get("todoPatterns").concat(
591 | configurations.get("additionalTODOPatterns")
592 | );
593 | filters.push(new TODOFilter(patterns));
594 | }
595 | if(filterKeys.length <= 0 || filterKeys.indexOf("note") >= 0){
596 | let patterns = configurations.get("notePatterns").concat(
597 | configurations.get("additionalNotePatterns")
598 | );
599 | filters.push(new NoteFilter(patterns));
600 | }
601 | if(!filters || filters.length <= 0){
602 | console.log("[Mark Jump] No filter available");
603 | return [];
604 | }
605 |
606 | if(editor){
607 | return this.getEditorMarks(editor, options, ...filters).then(
608 | resolve.bind(this)
609 | );
610 | }else if(
611 | withProjectWide &&
612 | configurations.get("showProjectMarks")
613 | ){
614 | return this.getWorkspaceMarks(...filters).then(
615 | resolve.bind(this)
616 | );
617 | }
618 |
619 | resolve([]);
620 | });
621 | }
622 |
623 | getEditorMarks(
624 | editor: vscode.TextEditor,
625 | options?: {
626 | offset: number;
627 | limit: number;
628 | },
629 | ...filters: MarkFilter[]
630 | ){
631 | return new Promise((resolve, reject) => {
632 | let items: MarkItem[] = [];
633 | let lineCount = editor.document.lineCount;
634 |
635 | let limit: number | undefined = options ? options.limit : undefined;
636 | let direction = limit === undefined || limit > 0 ? "down" : "up";
637 | let lineNumber = options ? options.offset : (
638 | direction === "down" ? 0 : lineCount - 1
639 | );
640 | while (lineNumber >= 0 && lineNumber < lineCount) {
641 | let lineText = editor.document.lineAt(lineNumber).text;
642 | let filter = filters.find(
643 | filter => filter.test(lineText)
644 | );
645 | if(!filter){
646 | if (direction === "down") {
647 | lineNumber += 1;
648 | } else {
649 | lineNumber -= 1;
650 | }
651 | continue;
652 | }
653 | let item = filter.getItem(
654 | editor.document.uri, lineNumber, lineText
655 | );
656 | if(!item){
657 | if (direction === "down") {
658 | lineNumber += 1;
659 | } else {
660 | lineNumber -= 1;
661 | }
662 | continue;
663 | }
664 | items.push(item);
665 |
666 | if (direction === "down") {
667 | lineNumber += 1;
668 | } else {
669 | lineNumber -= 1;
670 | }
671 | }
672 | resolve(items);
673 | });
674 | }
675 |
676 | getContentMarks(uri: vscode.Uri, ...filters: MarkFilter[]){
677 | let configurations = vscode.workspace.getConfiguration("markJump");
678 | let patterns = configurations.get("testPatterns").concat(
679 | configurations.get("additionalTestPatterns")
680 | );
681 |
682 | let items: MarkItem[] = [];
683 | let data = fs.readFileSync(uri.fsPath);
684 | let content = data.toString();
685 |
686 | let result = patterns.some(
687 | pattern => XRegExp.test(content, XRegExp(pattern))
688 | );
689 |
690 | if (!result) {
691 | return [];
692 | }
693 |
694 | let lines = content.split("\n");
695 |
696 | lines.forEach((lineText, lineNumber) => {
697 | let filter = filters.find(
698 | filter => filter.test(lineText)
699 | );
700 | if(!filter){
701 | return;
702 | }
703 | let item = filter.getItem(
704 | uri, lineNumber, lineText
705 | );
706 | if(!item){
707 | return;
708 | }
709 | items.push(item);
710 | });
711 |
712 | return items;
713 | }
714 |
715 | getWorkspaceMarks(...filters: MarkFilter[]){
716 | return new Promise((resolve, reject) => {
717 | let configurations = vscode.workspace.getConfiguration("markJump");
718 |
719 | vscode.workspace.findFiles(
720 | configurations.get("includeFilePattern"),
721 | configurations.get("excludeFilePattern")
722 | ).then(urls => {
723 | if (!urls) {
724 | return Promise.resolve([] as vscode.Uri[]);
725 | }
726 | let limit = configurations.get("maximumLimit");
727 | let strictLimit = configurations.get("strictLimit");
728 |
729 | if (limit < 0 || urls.length <= limit) {
730 | return Promise.resolve(urls);
731 | }
732 |
733 | if (strictLimit === "limit" || this.useLimit > 0) {
734 | return Promise.resolve(urls.slice(0, limit));
735 | } else if (strictLimit === "disable" || this.useLimit === 0) {
736 | return Promise.resolve([] as vscode.Uri[]);
737 | }
738 |
739 | if (Date.now() - this.lastWarning < 5000) {
740 | return Promise.resolve([] as vscode.Uri[]);
741 | }
742 |
743 | return vscode.window.showWarningMessage(
744 | `Mark Jump is going to run through ${
745 | urls.length
746 | } files, but the limit has set to ${
747 | limit
748 | }.`,
749 | {
750 | title: `Use ${ limit } for now`,
751 | action: () => limit,
752 | isCloseAffordance: true
753 | }, {
754 | title: `Set to ${ urls.length }`,
755 | action: () => configurations.update(
756 | "maximumLimit",
757 | urls.length,
758 | vscode.ConfigurationTarget.Global
759 | ),
760 | isCloseAffordance: true
761 | }, {
762 | title: `Set to unlimited`,
763 | danger: {
764 | title: "Increase the limit to unlimited? This could impact the editor's performance a lot.",
765 | action: "Yes, increase to unlimited",
766 | cancel: "Cancel"
767 | },
768 | action: () => configurations.update(
769 | "maximumLimit",
770 | -1,
771 | vscode.ConfigurationTarget.Global
772 | ),
773 | isCloseAffordance: true
774 | }, {
775 | title: `Disable for now`,
776 | action: () => 0,
777 | isCloseAffordance: true
778 | }, {
779 | title: `Close`,
780 | isCloseAffordance: true
781 | }
782 | ).then((action) => {
783 | if (!action) {
784 | this.lastWarning = Date.now();
785 | return Promise.resolve([] as vscode.Uri[]);
786 | }
787 |
788 | if (!action.danger) {
789 | this.lastWarning = Date.now();
790 | if (action.action) {
791 | let result = action.action();
792 | if (typeof(result) === "number") {
793 | this.useLimit = result;
794 | return Promise.resolve(urls.slice(0, result));
795 | } else {
796 | return result.then(
797 | () => Promise.resolve(urls)
798 | );
799 | }
800 | } else {
801 | return Promise.resolve([] as vscode.Uri[]);
802 | }
803 | }
804 | let danger = action.danger;
805 | return vscode.window.showWarningMessage(
806 | danger.title, {
807 | title: danger.action,
808 | action: action.action,
809 | isCloseAffordance: true
810 | }, {
811 | title: danger.cancel,
812 | isCloseAffordance: true
813 | }
814 | ).then((action) => {
815 | this.lastWarning = Date.now();
816 | if (!action) {
817 | return Promise.resolve([] as vscode.Uri[]);
818 | }
819 | if (action.action) {
820 | let result = action.action();
821 | if (typeof(result) === "number") {
822 | this.useLimit = result;
823 | return Promise.resolve(urls.slice(0, result));
824 | } else {
825 | return result.then(
826 | () => Promise.resolve(urls)
827 | );
828 | }
829 | } else {
830 | return Promise.resolve([] as vscode.Uri[]);
831 | }
832 | });
833 | });
834 | }).then((urls) => {
835 | if (urls.length === 0) {
836 | return resolve([]);
837 | }
838 | let items: MarkItem[] = [];
839 | urls.forEach(url => {
840 | try{
841 | items = items.concat(
842 | this.getContentMarks(url, ...filters)
843 | );
844 | }catch(error){
845 | return;
846 | }
847 | });
848 | resolve(items);
849 | })
850 | });
851 | }
852 | }
853 |
854 | class SectionFilter implements MarkFilter {
855 | patterns: string[];
856 |
857 | constructor(patterns: string[] = []){
858 | this.patterns = patterns;
859 | }
860 |
861 | test(lineText: string): boolean {
862 | return this.patterns.some(pattern => {
863 | return XRegExp.test(lineText, XRegExp(pattern));
864 | });
865 | }
866 |
867 | getItem(
868 | uri: vscode.Uri, lineNumber: number, lineText: string
869 | ): MarkItem | undefined {
870 | let item: MarkItem | undefined = undefined;
871 | this.patterns.forEach(pattern => {
872 | let matches = XRegExp.exec(lineText, XRegExp(pattern));
873 | if(!matches){
874 | return;
875 | }
876 | item = {
877 | uri: uri,
878 | type: "section",
879 | range: new vscode.Range(
880 | lineNumber, 0, lineNumber, lineText.length
881 | ),
882 | heading: matches["heading"],
883 | description: matches["description"],
884 | lineNumber: lineNumber
885 | };
886 | });
887 | return item;
888 | }
889 | }
890 |
891 | class TODOFilter implements MarkFilter {
892 | patterns: string[];
893 |
894 | constructor(patterns: string[] = []){
895 | this.patterns = patterns;
896 | }
897 |
898 | test(lineText: string): boolean {
899 | return this.patterns.some(pattern => {
900 | return XRegExp.test(lineText, XRegExp(pattern));
901 | });
902 | }
903 |
904 | getItem(
905 | uri: vscode.Uri, lineNumber: number, lineText: string
906 | ): MarkItem | undefined {
907 | let item: MarkItem | undefined = undefined;
908 | this.patterns.forEach(pattern => {
909 | let matches = XRegExp.exec(lineText, XRegExp(pattern));
910 | if(!matches){
911 | return;
912 | }
913 | item = {
914 | uri: uri,
915 | type: "todo",
916 | range: new vscode.Range(
917 | lineNumber, 0, lineNumber, lineText.length
918 | ),
919 | heading: matches["heading"],
920 | description: matches["description"],
921 | writer: matches["writer"],
922 | lineNumber: lineNumber
923 | };
924 | });
925 | return item;
926 | }
927 | }
928 |
929 | class NoteFilter implements MarkFilter {
930 | patterns: string[];
931 |
932 | constructor(patterns: string[] = []){
933 | this.patterns = patterns;
934 | }
935 |
936 | test(lineText: string): boolean {
937 | return this.patterns.some(pattern => {
938 | return XRegExp.test(lineText, XRegExp(pattern));
939 | });
940 | }
941 |
942 | getItem(
943 | uri: vscode.Uri, lineNumber: number, lineText: string
944 | ): MarkItem | undefined {
945 | let item: MarkItem | undefined = undefined;
946 | this.patterns.forEach(pattern => {
947 | let matches = XRegExp.exec(lineText, XRegExp(pattern));
948 | if(!matches){
949 | return;
950 | }
951 | item = {
952 | uri: uri,
953 | type: "note",
954 | range: new vscode.Range(
955 | lineNumber, 0, lineNumber, lineText.length
956 | ),
957 | heading: matches["heading"],
958 | description: matches["description"],
959 | writer: matches["writer"],
960 | lineNumber: lineNumber
961 | };
962 | });
963 | return item;
964 | }
965 | }
966 |
--------------------------------------------------------------------------------