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/src/extension.ts b/src/extension.ts index 03a12bc..01eafab 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; @@ -23,8 +24,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 +49,7 @@ const ANCHOR_MODE_LIST = "bitbucket.org", "ghost.org", "gitlab.com" -] +]; export function activate(context: ExtensionContext) { @@ -59,7 +60,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 +81,13 @@ class MarkdownTocTools { UPDATE_ON_SAVE : true, ANCHOR_MODE : ANCHOR_MODE_LIST[0] }; - optionsFlag = []; + optionsFlag : { [key: string]: boolean } = {}; saveBySelf = false; // Public function public updateMarkdownToc(isBySave : boolean = false) { + if (!window.activeTextEditor) return; + let editor = window.activeTextEditor; let markdownTocTools = this; @@ -92,11 +95,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,26 +113,29 @@ 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); }); } - public updateMarkdownSections() { 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) && !lineText.match(REGEXP_IGNORE_TITLE)) { 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,45 +206,47 @@ class MarkdownTocTools { this.options.ANCHOR_MODE = workspace.getConfiguration('markdown-toc').get('anchorMode'); } - private loadCustomOptions(tocRange : Range) { - this.optionsFlag = []; - if (tocRange == null) return; + private loadCustomOptions(tocRange : Range | null) { + 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; + + this.optionsFlag = {}; + 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]; 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; } @@ -245,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('')); @@ -253,10 +264,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); @@ -264,31 +276,26 @@ 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(tabSize === undefined || tabSize === null) { - tabSize = workspace.getConfiguration("editor").get("tabSize"); - } - if(insertSpaces === undefined || insertSpaces === null) { - insertSpaces = workspace.getConfiguration("editor").get("insertSpaces"); - } + if (!window.activeTextEditor) return; + + 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) { tab = " ".repeat(tabSize); - } + } let optionsText = []; optionsText.push('' + lineEnding); let text = []; @@ -302,7 +309,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 +319,7 @@ class MarkdownTocTools { waitResetList[index] = false; } } - + let row = [ tab.repeat(length), this.options.ORDERED_LIST ? (++indicesOfDepth[length] + '. ') : '- ', @@ -328,26 +335,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,24 +368,19 @@ class MarkdownTocTools { } indicesOfDepth[depth - 1]++; - let orderedListStr = "" + let orderedListStr = ""; for (var i = this.options.DEPTH_FROM - 1; i < depth; i++) { orderedListStr += indicesOfDepth[i].toString() + "."; } 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 - - if (hashMap[title] == null) { - hashMap[title] = 0 - } else { - hashMap[title] += 1; - } - - let hash = this.getHash(title, this.options.ANCHOR_MODE, hashMap[title]); + 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 + + let hash = this.getHash(title, this.options.ANCHOR_MODE, hashMap); headerList.push({ line : index, depth : depth, @@ -392,9 +395,39 @@ class MarkdownTocTools { return headerList; } - private getHash(headername : string, mode : string, repetition : number) { - let anchor = require('anchor-markdown-header'); - return decodeURI(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) { @@ -409,14 +442,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 e5187e7..e7bc158 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 */ + "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. */ + }, + "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).