From efdd49211c68d93381339a869a6a453dc27cbb54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Tue, 22 Sep 2020 11:54:09 +0200 Subject: [PATCH 1/8] Refresh VSC extension development environment - Add eslint - Update .vscode settings - Substitute vscode for @types/vscode and vscode-test packages - Update package.json scripts - Update tsconfig and remove typings folder - Update vsc-extension-quickstart document --- .eslintrc.json | 19 +++ .gitignore | 3 +- .vscode/extensions.json | 7 + .vscode/launch.json | 56 +++---- .vscode/settings.json | 17 ++- .vscode/tasks.json | 48 +++--- package.json | 282 ++++++++++++++++++------------------ tsconfig.json | 31 ++-- typings/node.d.ts | 1 - typings/vscode-typings.d.ts | 1 - vsc-extension-quickstart.md | 51 ++++--- 11 files changed, 280 insertions(+), 236 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .vscode/extensions.json delete mode 100644 typings/node.d.ts delete mode 100644 typings/vscode-typings.d.ts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c86b9b2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/naming-convention": "off", + "@typescript-eslint/semi": "warn", + "curly": "off", + "eqeqeq": "warn", + "no-throw-literal": "warn", + "semi": "off" + } +} diff --git a/.gitignore b/.gitignore index 8e5962e..1294fe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ out -node_modules \ No newline at end of file +node_modules +package-lock.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..3ac9aeb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index c77b2ad..670d6e6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,28 +1,34 @@ // A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.1.0", - "configurations": [ - { - "name": "Launch Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out/src", - "preLaunchTask": "npm" - }, - { - "name": "Launch Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out/test", - "preLaunchTask": "npm" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 7877e3f..afdab66 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,11 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version -} \ No newline at end of file + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fb7f662..3b17e53 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,30 +1,20 @@ -// Available variables which can be used inside of strings. -// ${workspaceRoot}: the root folder of the team -// ${file}: the current opened file -// ${fileBasename}: the current opened file's basename -// ${fileDirname}: the current opened file's dirname -// ${fileExtname}: the current opened file's extension -// ${cwd}: the current working directory of the spawned process - -// A task runner that calls a custom npm script that compiles the extension. +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format { - "version": "0.1.0", - - // we want to run npm - "command": "npm", - - // the command is a shell script - "isShellCommand": true, - - // show the output window only if unrecognized errors occur. - "showOutput": "silent", - - // we run the custom script "compile" as defined in package.json - "args": ["run", "compile", "--loglevel", "silent"], - - // The tsc compiler is started in watching mode - "isWatching": true, - - // use the standard tsc in watch mode problem matcher to find compile problems in the output. - "problemMatcher": "$tsc-watch" -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/package.json b/package.json index a7f22d4..06a23fb 100644 --- a/package.json +++ b/package.json @@ -1,145 +1,151 @@ { - "name": "markdown-toc", - "displayName": "Markdown TOC", - "description": "Markdown TOC(Table Of Contents) Plugin for Visual Studio Code.", - "version": "1.6.0", - "icon": "img/markdown-toc.png", - "license": "MIT", - "author": { - "email": "alanwalk93@gmail.com", - "name": "Alan Walk", - "url": "https://blog.otorb.com" - }, - "bugs": { - "url": "https://github.com/AlanWalk/Markdown-TOC/issues" - }, - "repository": { - "type": "git", - "url": "https://github.com/AlanWalk/Markdown-TOC.git" - }, - "keywords": [ - "markdown", - "toc" - ], - "homepage": "https://github.com/AlanWalk/Markdown-TOC", - "publisher": "AlanWalk", - "engines": { - "vscode": "^1.0.0" - }, - "categories": [ - "Other" - ], - "activationEvents": [ - "onLanguage:markdown" - ], - "main": "./out/src/extension", - "contributes": { - "commands": [ - { - "command": "extension.updateMarkdownToc", - "title": "Markdown TOC: Insert/Update" - }, - { - "command": "extension.deleteMarkdownToc", - "title": "Markdown TOC: Delete" - }, - { - "command": "extension.updateMarkdownSections", - "title": "Markdown Sections: Insert/Update" - }, - { - "command": "extension.deleteMarkdownSections", - "title": "Markdown Sections: Delete" - }], - "menus": { - "editor/context": [ - { - "when": "editorLangId == 'markdown'", - "command": "extension.updateMarkdownToc" - }, - { - "when": "editorLangId == 'markdown'", - "command": "extension.deleteMarkdownToc" - }, - { - "when": "editorLangId == 'markdown'", - "command": "extension.updateMarkdownSections" - }, - { - "when": "editorLangId == 'markdown'", - "command": "extension.deleteMarkdownSections" - } - ] - }, - "keybindings":[ - { - "command": "extension.updateMarkdownToc", - "key": "ctrl+m t" - }, - { - "command": "extension.updateMarkdownSections", - "key": "ctrl+m s" - } - ], - "configuration": { - "type": "object", - "title": "Markdown TOC configuration", - "properties": { - "markdown-toc.depthFrom": { - "type": "number", - "default": 1, - "description": "Depth control [1-6]." - }, - "markdown-toc.depthTo": { - "type": "number", - "default": 6, - "description": "Depth control [1-6]." - }, - "markdown-toc.insertAnchor": { - "type": "boolean", - "default": false, - "description": "Auto insert anchor for link." - }, - "markdown-toc.withLinks": { - "type": "boolean", - "default": true, - "description": "Auto insert link." - }, - "markdown-toc.orderedList": { - "type": "boolean", - "default": false, - "description": "Use ordered list (1. ..., 2. ...)." - }, - "markdown-toc.updateOnSave": { - "type": "boolean", - "default": true, - "description": "Auto update on save." - }, - "markdown-toc.anchorMode": { - "type": "string", - "default": "github.com", - "description": "anchor mode.", + "name": "markdown-toc", + "displayName": "Markdown TOC", + "description": "Markdown TOC(Table Of Contents) Plugin for Visual Studio Code.", + "version": "1.6.0", + "icon": "img/markdown-toc.png", + "license": "MIT", + "author": { + "email": "alanwalk93@gmail.com", + "name": "Alan Walk", + "url": "https://blog.otorb.com" + }, + "bugs": { + "url": "https://github.com/AlanWalk/Markdown-TOC/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/AlanWalk/Markdown-TOC.git" + }, + "keywords": [ + "markdown", + "toc" + ], + "homepage": "https://github.com/AlanWalk/Markdown-TOC", + "publisher": "AlanWalk", + "engines": { + "vscode": "^1.0.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onLanguage:markdown" + ], + "main": "./out/src/extension", + "contributes": { + "commands": [ + { + "command": "extension.updateMarkdownToc", + "title": "Markdown TOC: Insert/Update" + }, + { + "command": "extension.deleteMarkdownToc", + "title": "Markdown TOC: Delete" + }, + { + "command": "extension.updateMarkdownSections", + "title": "Markdown Sections: Insert/Update" + }, + { + "command": "extension.deleteMarkdownSections", + "title": "Markdown Sections: Delete" + } + ], + "menus": { + "editor/context": [ + { + "when": "editorLangId == 'markdown'", + "command": "extension.updateMarkdownToc" + }, + { + "when": "editorLangId == 'markdown'", + "command": "extension.deleteMarkdownToc" + }, + { + "when": "editorLangId == 'markdown'", + "command": "extension.updateMarkdownSections" + }, + { + "when": "editorLangId == 'markdown'", + "command": "extension.deleteMarkdownSections" + } + ] + }, + "keybindings": [ + { + "command": "extension.updateMarkdownToc", + "key": "ctrl+m t" + }, + { + "command": "extension.updateMarkdownSections", + "key": "ctrl+m s" + } + ], + "configuration": { + "type": "object", + "title": "Markdown TOC configuration", + "properties": { + "markdown-toc.depthFrom": { + "type": "number", + "default": 1, + "description": "Depth control [1-6]." + }, + "markdown-toc.depthTo": { + "type": "number", + "default": 6, + "description": "Depth control [1-6]." + }, + "markdown-toc.insertAnchor": { + "type": "boolean", + "default": false, + "description": "Auto insert anchor for link." + }, + "markdown-toc.withLinks": { + "type": "boolean", + "default": true, + "description": "Auto insert link." + }, + "markdown-toc.orderedList": { + "type": "boolean", + "default": false, + "description": "Use ordered list (1. ..., 2. ...)." + }, + "markdown-toc.updateOnSave": { + "type": "boolean", + "default": true, + "description": "Auto update on save." + }, + "markdown-toc.anchorMode": { + "type": "string", + "default": "github.com", + "description": "anchor mode.", "enum": [ "github.com", "bitbucket.org", "ghost.org", - "gitlab.com" + "gitlab.com" ] - } - } - } - }, - "scripts": { - "vscode:prepublish": "node ./node_modules/vscode/bin/compile", - "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install" - }, - "dependencies": { - "anchor-markdown-header": "^0.5.7" - }, - "devDependencies": { - "anchor-markdown-header": "^0.5.7", - "typescript": "^1.8.5", - "vscode": "^0.11.0" - } + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "lint": "eslint src --ext ts", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "anchor-markdown-header": "^0.5.7" + }, + "devDependencies": { + "@types/node": "^14.11.1", + "@types/vscode": "^1.49.0", + "@typescript-eslint/eslint-plugin": "^4.2.0", + "@typescript-eslint/parser": "^4.2.0", + "eslint": "^7.9.0", + "typescript": "^4.0.3", + "vscode-test": "^1.0.0" + } } diff --git a/tsconfig.json b/tsconfig.json index e5187e7..d9e779a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,20 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "outDir": "out", - "noLib": true, - "sourceMap": true, - "rootDir": "." - }, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": ".", + "strict": true /* enable all strict type-checking options */ + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + }, + "exclude": [ + "node_modules" + ] +} diff --git a/typings/node.d.ts b/typings/node.d.ts deleted file mode 100644 index 5ed7730..0000000 --- a/typings/node.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/typings/vscode-typings.d.ts b/typings/vscode-typings.d.ts deleted file mode 100644 index 5590dc8..0000000 --- a/typings/vscode-typings.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/vsc-extension-quickstart.md b/vsc-extension-quickstart.md index 6cdea2b..b510bff 100644 --- a/vsc-extension-quickstart.md +++ b/vsc-extension-quickstart.md @@ -1,33 +1,42 @@ -# Welcome to your first VS Code Extension +# Welcome to your VS Code Extension ## What's in the folder -* This folder contains all of the files necessary for your extension + +* This folder contains all of the files necessary for your extension. * `package.json` - this is the manifest file in which you declare your extension and command. -The sample plugin registers a command and defines its title and command name. With this information -VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. + * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. * `src/extension.ts` - this is the main file where you will provide the implementation of your command. -The file exports one function, `activate`, which is called the very first time your extension is -activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. -We pass the function containing the implementation of the command as the second parameter to -`registerCommand`. + * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. ## Get up and running straight away -* press `F5` to open a new window with your extension loaded -* run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` -* set breakpoints in your code inside `src/extension.ts` to debug your extension -* find output from your extension in the debug console + +* Press `F5` to open a new window with your extension loaded. +* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. +* Set breakpoints in your code inside `src/extension.ts` to debug your extension. +* Find output from your extension in the debug console. ## Make changes -* you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` -* you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes + +* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. + ## Explore the API -* you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` + +* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. ## Run tests -* open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` -* press `F5` to run the tests in a new window with your extension loaded -* see the output of the test result in the debug console -* make changes to `test/extension.test.ts` or create new test files inside the `test` folder - * by convention, the test runner will only consider files matching the name pattern `**.test.ts` - * you can create folders inside the `test` folder to structure your tests any way you want \ No newline at end of file + +* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. +* Press `F5` to run the tests in a new window with your extension loaded. +* See the output of the test result in the debug console. +* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. + * The provided test runner will only consider files matching the name pattern `**.test.ts`. + * You can create folders inside the `test` folder to structure your tests any way you want. + +## Go further + + * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). + * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. + * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). From 5742a633c8474e768ef629aa84667ad3ea601cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Tue, 22 Sep 2020 13:05:31 +0200 Subject: [PATCH 2/8] Fix TypeScript errors and linting warnings --- src/extension.ts | 114 ++++++++++++++++++++++++++--------------------- tsconfig.json | 4 +- 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 03a12bc..c7df415 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,8 +23,8 @@ const REGEXP_MARKDOWN_ANCHOR = /^<\/a\>/; const REGEXP_HEADER = /^(\#{1,6})\s*(.+)/; const REGEXP_CODE_BLOCK1 = /^```/; const REGEXP_CODE_BLOCK2 = /^~~~/; -const REGEXP_ANCHOR = /\[.+\]\(#(.+)\)/ -const REGEXP_IGNORE_TITLE = // +const REGEXP_ANCHOR = /\[.+\]\(#(.+)\)/; +const REGEXP_IGNORE_TITLE = //; const DEPTH_FROM = "depthFrom"; const DEPTH_TO = "depthTo"; @@ -48,7 +48,7 @@ const ANCHOR_MODE_LIST = "bitbucket.org", "ghost.org", "gitlab.com" -] +]; export function activate(context: ExtensionContext) { @@ -59,7 +59,7 @@ export function activate(context: ExtensionContext) { let disposable_deleteMarkdownToc = commands.registerCommand('extension.deleteMarkdownToc', () => { markdownTocTools.deleteMarkdownToc(); }); let disposable_updateMarkdownSections = commands.registerCommand('extension.updateMarkdownSections', () => { markdownTocTools.updateMarkdownSections(); }); let disposable_deleteMarkdownSections = commands.registerCommand('extension.deleteMarkdownSections', () => { markdownTocTools.deleteMarkdownSections(); }); - let disposable_saveMarkdownToc = workspace.onDidSaveTextDocument((doc : TextDocument) => { markdownTocTools.notifyDocumentSave(); }); + let disposable_saveMarkdownToc = workspace.onDidSaveTextDocument((doc : TextDocument) => { markdownTocTools.notifyDocumentSave(doc); }); // Add to a list of disposables which are disposed when this extension is deactivated. context.subscriptions.push(disposable_updateMarkdownToc); @@ -80,11 +80,13 @@ class MarkdownTocTools { UPDATE_ON_SAVE : true, ANCHOR_MODE : ANCHOR_MODE_LIST[0] }; - optionsFlag = []; + optionsFlag : string[] = []; saveBySelf = false; // Public function public updateMarkdownToc(isBySave : boolean = false) { + if (!window.activeTextEditor) return; + let editor = window.activeTextEditor; let markdownTocTools = this; @@ -92,11 +94,11 @@ class MarkdownTocTools { let tocRange = markdownTocTools.getTocRange(); markdownTocTools.updateOptions(tocRange); - if (isBySave && ((!markdownTocTools.options.UPDATE_ON_SAVE) || (tocRange == null))) return false; + if (isBySave && (!markdownTocTools.options.UPDATE_ON_SAVE || tocRange === null)) return false; let insertPosition = editor.selection.active; // save options, and delete last insert - if (tocRange != null) { + if (tocRange !== null) { insertPosition = tocRange.start; editBuilder.delete(tocRange); markdownTocTools.deleteAnchor(editBuilder); @@ -110,11 +112,13 @@ class MarkdownTocTools { } public deleteMarkdownToc() { + if (!window.activeTextEditor) return; + let markdownTocTools = this; window.activeTextEditor.edit(function(editBuilder) { let tocRange = markdownTocTools.getTocRange(); - if (tocRange == null) return; + if (tocRange === null) return; editBuilder.delete(tocRange); markdownTocTools.deleteAnchor(editBuilder); @@ -126,10 +130,12 @@ class MarkdownTocTools { let tocRange = this.getTocRange(); this.updateOptions(tocRange); let headerList = this.getHeaderList(); - + + if (!window.activeTextEditor) return; + window.activeTextEditor.edit(function(editBuilder) { - headerList.forEach(element => { - let newHeader = element.header + " " + element.orderedList + " " + element.baseTitle + headerList.forEach(element => { + let newHeader = element.header + " " + element.orderedList + " " + element.baseTitle; editBuilder.replace(element.range, newHeader); }); }); @@ -140,22 +146,23 @@ class MarkdownTocTools { this.updateOptions(tocRange); let headerList = this.getHeaderList(); + if (!window.activeTextEditor) return; + window.activeTextEditor.edit(function(editBuilder) { headerList.forEach(element => { - let newHeader = element.header + " " + element.baseTitle + let newHeader = element.header + " " + element.baseTitle; editBuilder.replace(element.range, newHeader); }); }); } - public notifyDocumentSave() { + public notifyDocumentSave(doc: TextDocument) { // Prevent save again if (this.saveBySelf) { this.saveBySelf = false; return; } - let doc = window.activeTextEditor.document; - if (doc.languageId != 'markdown') return; + if (doc.languageId !== 'markdown') return; if (this.updateMarkdownToc(true)) { doc.save(); this.saveBySelf = true; @@ -164,25 +171,27 @@ class MarkdownTocTools { // Private function private getTocRange() { + if (!window.activeTextEditor) return null; let doc = window.activeTextEditor.document; - let start, stop : Position; + let start = null; + let stop = null; - for(let index = 0; index < doc.lineCount; index++) { + for (let index = 0; index < doc.lineCount; index++) { let lineText = doc.lineAt(index).text; - if ((start == null) && (lineText.match(REGEXP_TOC_START))) { + if ((start === null) && (lineText.match(REGEXP_TOC_START))) { start = new Position(index, 0); } else if (lineText.match(REGEXP_TOC_STOP)) { stop = new Position(index, lineText.length); break; } } - if ((start != null) && (stop != null)) { + if ((start !== null) && (stop !== null)) { return new Range(start, stop); } return null; } - private updateOptions(tocRange : Range) { + private updateOptions(tocRange : Range | null) { this.loadConfigurations(); this.loadCustomOptions(tocRange); } @@ -197,15 +206,16 @@ class MarkdownTocTools { this.options.ANCHOR_MODE = workspace.getConfiguration('markdown-toc').get('anchorMode'); } - private loadCustomOptions(tocRange : Range) { + private loadCustomOptions(tocRange : Range | null) { this.optionsFlag = []; - if (tocRange == null) return; + if (tocRange === null || !window.activeTextEditor) return; let optionsText = window.activeTextEditor.document.lineAt(tocRange.start.line).text; let options = optionsText.match(REGEXP_TOC_CONFIG); - if (options == null) return; - + if (options === null) return; + options.forEach(element => { - let pair = REGEXP_TOC_CONFIG_ITEM.exec(element) + let pair = REGEXP_TOC_CONFIG_ITEM.exec(element); + if (pair === null) return; let key = pair[1].toLocaleLowerCase(); let value = pair[2]; @@ -253,10 +263,11 @@ class MarkdownTocTools { } private deleteAnchor(editBuilder : TextEditorEdit) { + if (!window.activeTextEditor) return; let doc = window.activeTextEditor.document; for(let index = 0; index < doc.lineCount; index++) { let lineText = doc.lineAt(index).text; - if(lineText.match(REGEXP_MARKDOWN_ANCHOR) == null) continue; + if(lineText.match(REGEXP_MARKDOWN_ANCHOR) === null) continue; let range = new Range(new Position(index, 0), new Position(index + 1, 0)); editBuilder.delete(range); @@ -267,7 +278,7 @@ class MarkdownTocTools { let lineEnding = workspace.getConfiguration("files").get("eol"); let tabSize = workspace.getConfiguration("[markdown]")["editor.tabSize"]; let insertSpaces = workspace.getConfiguration("[markdown]")["editor.insertSpaces"]; - + if(tabSize === undefined || tabSize === null) { tabSize = workspace.getConfiguration("editor").get("tabSize"); } @@ -278,17 +289,17 @@ class MarkdownTocTools { let tab = '\t'; if (insertSpaces && tabSize > 0) { tab = " ".repeat(tabSize); - } + } let optionsText = []; optionsText.push('' + lineEnding); let text = []; @@ -302,7 +313,7 @@ class MarkdownTocTools { minDepth = Math.min(element.depth, minDepth); }); let startDepth = Math.max(minDepth , this.options.DEPTH_FROM); - + headerList.forEach(element => { if (element.depth <= this.options.DEPTH_TO) { let length = element.depth - startDepth; @@ -312,7 +323,7 @@ class MarkdownTocTools { waitResetList[index] = false; } } - + let row = [ tab.repeat(length), this.options.ORDERED_LIST ? (++indicesOfDepth[length] + '. ') : '- ', @@ -328,26 +339,27 @@ class MarkdownTocTools { } private getHeaderList() { + if (!window.activeTextEditor) return []; let doc = window.activeTextEditor.document; let headerList = []; - let hashMap = {}; + let hashMap : { [key: string]: number } = {}; let isInCode = 0; let indicesOfDepth = Array.apply(null, new Array(6)).map(Number.prototype.valueOf, 0); for (let index = 0; index < doc.lineCount; index++) { let lineText = doc.lineAt(index).text; let codeResult1 = lineText.match(REGEXP_CODE_BLOCK1); let codeResult2 = lineText.match(REGEXP_CODE_BLOCK2); - if (isInCode == 0) { - isInCode = codeResult1 != null ? 1 : (codeResult2 != null ? 2 : isInCode); - } else if (isInCode == 1) { - isInCode = codeResult1 != null ? 0 : isInCode; - } else if (isInCode == 2) { - isInCode = codeResult2 != null ? 0 : isInCode; + if (isInCode === 0) { + isInCode = codeResult1 !== null ? 1 : (codeResult2 !== null ? 2 : isInCode); + } else if (isInCode === 1) { + isInCode = codeResult1 !== null ? 0 : isInCode; + } else if (isInCode === 2) { + isInCode = codeResult2 !== null ? 0 : isInCode; } if (isInCode) continue; let headerResult = lineText.match(REGEXP_HEADER); - if (headerResult == null) continue; + if (headerResult === null) continue; let depth = headerResult[1].length; if (depth < this.options.DEPTH_FROM) continue; @@ -360,7 +372,7 @@ class MarkdownTocTools { } indicesOfDepth[depth - 1]++; - let orderedListStr = "" + let orderedListStr = ""; for (var i = this.options.DEPTH_FROM - 1; i < depth; i++) { orderedListStr += indicesOfDepth[i].toString() + "."; } @@ -371,12 +383,12 @@ class MarkdownTocTools { title = title.replace(//gi, ""); // replace comment title = title.replace(/\#*_/gi, "").trim(); // replace special char - if (hashMap[title] == null) { - hashMap[title] = 0 + if (!(title in hashMap)) { + hashMap[title] = 0; } else { hashMap[title] += 1; } - + let hash = this.getHash(title, this.options.ANCHOR_MODE, hashMap[title]); headerList.push({ line : index, @@ -409,14 +421,14 @@ class MarkdownTocTools { } private parseValidAnchorMode(value : string) { - if (ANCHOR_MODE_LIST.indexOf(value) != -1) { + if (ANCHOR_MODE_LIST.indexOf(value) !== -1) { return value; } return ANCHOR_MODE_LIST[0]; } private parseBool(value : string) { - return value.toLocaleLowerCase() == 'true'; + return value.toLocaleLowerCase() === 'true'; } dispose() { diff --git a/tsconfig.json b/tsconfig.json index d9e779a..e7bc158 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,11 +8,11 @@ ], "sourceMap": true, "rootDir": ".", - "strict": true /* enable all strict type-checking options */ + "strict": true, /* enable all strict type-checking options */ + "noUnusedParameters": true /* Report errors on unused parameters. */ /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ }, "exclude": [ "node_modules" From 552f44732c148fe68f880372300751c547f5dcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Tue, 22 Sep 2020 12:05:19 +0200 Subject: [PATCH 3/8] Use line ending and tab settings of the file currently open --- src/extension.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index c7df415..9b6be03 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,8 @@ import { Position, Range, TextEditor, - TextEditorEdit + TextEditorEdit, + EndOfLine } from 'vscode'; const REGEXP_TOC_START = /\s*/gi; @@ -275,16 +276,11 @@ class MarkdownTocTools { } private createToc(editBuilder : TextEditorEdit, headerList : any[], insertPosition : Position) { - let lineEnding = workspace.getConfiguration("files").get("eol"); - let tabSize = workspace.getConfiguration("[markdown]")["editor.tabSize"]; - let insertSpaces = workspace.getConfiguration("[markdown]")["editor.insertSpaces"]; + if (!window.activeTextEditor) return; - if(tabSize === undefined || tabSize === null) { - tabSize = workspace.getConfiguration("editor").get("tabSize"); - } - if(insertSpaces === undefined || insertSpaces === null) { - insertSpaces = workspace.getConfiguration("editor").get("insertSpaces"); - } + let lineEnding = window.activeTextEditor.document.eol === EndOfLine.LF ? '\n' : '\r\n'; + let tabSize = window.activeTextEditor.options.tabSize as number; + let insertSpaces = window.activeTextEditor.options.insertSpaces as boolean; let tab = '\t'; if (insertSpaces && tabSize > 0) { From d1cbfd85a392006482f4b6063c5e59ab3ce5ed98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Tue, 22 Sep 2020 13:35:30 +0200 Subject: [PATCH 4/8] Remove bold and italic marks safely (only if at word boundaries) Fixes wrong removal of _ in headers introduced in fe9b778 Fixes #30 --- src/extension.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 9b6be03..a32598d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -126,7 +126,6 @@ class MarkdownTocTools { }); } - public updateMarkdownSections() { let tocRange = this.getTocRange(); this.updateOptions(tocRange); @@ -375,9 +374,10 @@ class MarkdownTocTools { let title = lineText.substr(depth).trim(); let baseTitle = title.replace(/^(?:\d+\.)+/, "").trim(); // title without section number - title = title.replace(/\[(.+)]\([^)]*\)/gi, "$1"); // replace link - title = title.replace(//gi, ""); // replace comment - title = title.replace(/\#*_/gi, "").trim(); // replace special char + title = title.replace(/\[(.+)]\([^)]*\)/gi, "$1"); // replace link + title = title.replace(//gi, ""); // replace comment + title = title.replace(/\#/gi, "").trim(); // replace special char + title = title.replace(/\b[_*]|[*_]\b/gi, ""); // replace bold and italic marks if (!(title in hashMap)) { hashMap[title] = 0; From 6f24e01a35d2b48821b1e277d6da4a886bcef76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Tue, 22 Sep 2020 12:04:26 +0200 Subject: [PATCH 5/8] Simplify optionsFlag property operations --- src/extension.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index a32598d..c50e1d9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -81,7 +81,7 @@ class MarkdownTocTools { UPDATE_ON_SAVE : true, ANCHOR_MODE : ANCHOR_MODE_LIST[0] }; - optionsFlag : string[] = []; + optionsFlag : { [key: string]: boolean } = {}; saveBySelf = false; // Public function @@ -207,12 +207,13 @@ class MarkdownTocTools { } private loadCustomOptions(tocRange : Range | null) { - this.optionsFlag = []; if (tocRange === null || !window.activeTextEditor) return; let optionsText = window.activeTextEditor.document.lineAt(tocRange.start.line).text; let options = optionsText.match(REGEXP_TOC_CONFIG); if (options === null) return; + this.optionsFlag = {}; + options.forEach(element => { let pair = REGEXP_TOC_CONFIG_ITEM.exec(element); if (pair === null) return; @@ -221,31 +222,31 @@ class MarkdownTocTools { switch (key) { case LOWER_DEPTH_FROM: - this.optionsFlag.push(DEPTH_FROM); + this.optionsFlag[DEPTH_FROM] = true; this.options.DEPTH_FROM = this.parseValidNumber(value); break; case LOWER_DEPTH_TO: - this.optionsFlag.push(DEPTH_TO); + this.optionsFlag[DEPTH_TO] = true; this.options.DEPTH_TO = Math.max(this.parseValidNumber(value), this.options.DEPTH_FROM); break; case LOWER_INSERT_ANCHOR: - this.optionsFlag.push(INSERT_ANCHOR); + this.optionsFlag[INSERT_ANCHOR] = true; this.options.INSERT_ANCHOR = this.parseBool(value); break; case LOWER_WITH_LINKS: - this.optionsFlag.push(WITH_LINKS); + this.optionsFlag[WITH_LINKS] = true; this.options.WITH_LINKS = this.parseBool(value); break; case LOWER_ORDERED_LIST: - this.optionsFlag.push(ORDERED_LIST); + this.optionsFlag[ORDERED_LIST] = true; this.options.ORDERED_LIST = this.parseBool(value); break; case LOWER_UPDATE_ON_SAVE: - this.optionsFlag.push(UPDATE_ON_SAVE); + this.optionsFlag[UPDATE_ON_SAVE] = true; this.options.UPDATE_ON_SAVE = this.parseBool(value); break; case LOWER_ANCHOR_MODE: - this.optionsFlag.push(ANCHOR_MODE); + this.optionsFlag[ANCHOR_MODE] = true; this.options.ANCHOR_MODE = this.parseValidAnchorMode(value); break; } @@ -288,13 +289,13 @@ class MarkdownTocTools { let optionsText = []; optionsText.push('' + lineEnding); let text = []; From b303631cd9fa210359a26272f245281635ddcb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Wed, 23 Sep 2020 20:35:33 +0200 Subject: [PATCH 6/8] Fix ignore title comment matching start of the TOC Fixes https://github.com/AlanWalk/markdown-toc/issues/26#issuecomment-466784656 --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index c50e1d9..72ee895 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -178,7 +178,7 @@ class MarkdownTocTools { for (let index = 0; index < doc.lineCount; index++) { let lineText = doc.lineAt(index).text; - if ((start === null) && (lineText.match(REGEXP_TOC_START))) { + if (start === null && lineText.match(REGEXP_TOC_START) && !lineText.match(REGEXP_IGNORE_TITLE)) { start = new Position(index, 0); } else if (lineText.match(REGEXP_TOC_STOP)) { stop = new Position(index, lineText.length); From 93db8fd5e16bc4cdd9853ac5ba279131e4f5bb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Wed, 23 Sep 2020 23:13:20 +0200 Subject: [PATCH 7/8] Remove decodeURI call which breaks on headings containing % Fixes #35 --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 72ee895..4976714 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -403,7 +403,7 @@ class MarkdownTocTools { private getHash(headername : string, mode : string, repetition : number) { let anchor = require('anchor-markdown-header'); - return decodeURI(anchor(headername, mode, repetition)); + return anchor(headername, mode, repetition); } private parseValidNumber(value : string) { From 9bbcb2f435ef17ac420c0d6e4f2ba84d127c07be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Cornell=C3=A0?= Date: Mon, 2 Nov 2020 18:39:14 +0100 Subject: [PATCH 8/8] Count link repetitions from the hash part, not the title This fixes the case where different titles get the same hash. In the old version, the repetition number was tracked with the title; since they didn't match, the repetition number wasn't incremented. Now, the repetition number is tracked from the hash (#title), so they get the correct repetition. Fixes #93 --- src/extension.ts | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4976714..01eafab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,7 +24,7 @@ const REGEXP_MARKDOWN_ANCHOR = /^<\/a\>/; const REGEXP_HEADER = /^(\#{1,6})\s*(.+)/; const REGEXP_CODE_BLOCK1 = /^```/; const REGEXP_CODE_BLOCK2 = /^~~~/; -const REGEXP_ANCHOR = /\[.+\]\(#(.+)\)/; +const REGEXP_ANCHOR = /^\[([^\]]+)\]\(#(.+)\)$/; const REGEXP_IGNORE_TITLE = //; const DEPTH_FROM = "depthFrom"; @@ -256,7 +256,7 @@ class MarkdownTocTools { private insertAnchor(editBuilder : TextEditorEdit, headerList : any[]) { if (!this.options.INSERT_ANCHOR) return; headerList.forEach(element => { - let name = element.hash.match(REGEXP_ANCHOR)[1]; + let name = element.hash.match(REGEXP_ANCHOR)[2]; let text = [ '\n' ]; let insertPosition = new Position(element.line, 0); editBuilder.insert(insertPosition, text.join('')); @@ -380,13 +380,7 @@ class MarkdownTocTools { title = title.replace(/\#/gi, "").trim(); // replace special char title = title.replace(/\b[_*]|[*_]\b/gi, ""); // replace bold and italic marks - if (!(title in hashMap)) { - hashMap[title] = 0; - } else { - hashMap[title] += 1; - } - - let hash = this.getHash(title, this.options.ANCHOR_MODE, hashMap[title]); + let hash = this.getHash(title, this.options.ANCHOR_MODE, hashMap); headerList.push({ line : index, depth : depth, @@ -401,9 +395,39 @@ class MarkdownTocTools { return headerList; } - private getHash(headername : string, mode : string, repetition : number) { - let anchor = require('anchor-markdown-header'); - return anchor(headername, mode, repetition); + private getHash(headername : string, mode : string, hashMap: { [key: string]: number }) { + // Get the link format for headername (force repetition = 0) + let anchor = require('anchor-markdown-header')(headername, mode, 0); + + // Decompose the anchor into its two components + let match = anchor.match(REGEXP_ANCHOR); + if (!match || match.length < 3) return anchor; + let [title, hash] = match.slice(1, 3); + + // Check if the hash is repeated + if (!(hash in hashMap)) { + hashMap[hash] = 0; + } else { + hashMap[hash] += 1; + + // Add the repetition number to the hash + switch (mode) { + case "github.com": + hash = `${hash}-${hashMap[hash]}`; + break; + case "bitbucket.org": + hash = `${hash}_${hashMap[hash]}`; + break; + case "ghost.org": + hash = `${hash}-${hashMap[hash]}`; + break; + case "gitlab.com": + hash = `${hash}-${hashMap[hash]}`; + break; + } + } + + return `[${title}](#${hash})`; } private parseValidNumber(value : string) {