├── .eslintrc.js ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── images ├── Tcl-powered.png ├── snippets.gif └── syntax-highlight.png ├── language-configuration.json ├── package-lock.json ├── package.json ├── snippets └── tcl.json ├── src ├── documentHelpers.ts ├── extension.ts ├── formatProvider.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ ├── formatProvider.test.ts │ └── index.ts ├── syntaxes └── tcl.tmlanguage.yaml ├── tsconfig.json ├── tslint.json └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "project": "tsconfig.json", 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | "@typescript-eslint/class-name-casing": "warn", 20 | "@typescript-eslint/member-delimiter-style": [ 21 | "warn", 22 | { 23 | "multiline": { 24 | "delimiter": "semi", 25 | "requireLast": true 26 | }, 27 | "singleline": { 28 | "delimiter": "semi", 29 | "requireLast": false 30 | } 31 | } 32 | ], 33 | "@typescript-eslint/no-unused-expressions": "warn", 34 | "@typescript-eslint/semi": [ 35 | "warn", 36 | "always" 37 | ], 38 | "curly": "warn", 39 | "eqeqeq": [ 40 | "warn", 41 | "always" 42 | ], 43 | "no-redeclare": "warn", 44 | "no-throw-literal": "warn" 45 | } 46 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [{ 8 | "name": "Run Extension", 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "runtimeExecutable": "${execPath}", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: watch" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "runtimeExecutable": "${execPath}", 25 | "args": [ 26 | "--extensionDevelopmentPath=${workspaceFolder}", 27 | "--extensionTestsPath=${workspaceFolder}/out/test" 28 | ], 29 | "outFiles": [ 30 | "${workspaceFolder}/out/test/**/*.js" 31 | ], 32 | "preLaunchTask": "npm: watch" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.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 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/.eslintrc.js 10 | **/*.ts 11 | syntaxes/** 12 | Makefile 13 | .yo-rc.json 14 | tsconfig.tsbuildinfo 15 | src/build.ts 16 | .eslintrc.js 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.3 4 | Added a command to escape selections to a Tcl quoted string with escaping. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 James Deucker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BASENAME?=tcl 2 | NAME?=$(BASENAME) 3 | VERSION?=$(shell jq -r .version package.json) 4 | VSIX?=$(BASENAME)-$(VERSION).vsix 5 | PKG_ID?=bitwisecook.$(BASENAME) 6 | 7 | .DEFAULT_GOAL := vsix 8 | 9 | node_modules/: 10 | npm install 11 | 12 | out/%.js: src/%.ts node_modules/ out/syntaxes 13 | npm run webpack 14 | 15 | clean: 16 | rm -rf out $(VSIX) 17 | 18 | dist-clean: clean 19 | rm -rf node_modules 20 | 21 | build: out/extension.js 22 | 23 | install: package 24 | code --install-extension $(VSIX) 25 | 26 | uninstall: 27 | code --uninstall-extension $(PKG_ID) 28 | 29 | package: $(VSIX) 30 | 31 | $(VSIX): syntax 32 | npx vsce package 33 | 34 | vsix: $(VSIX) 35 | 36 | test: out/%.js 37 | npm test 38 | 39 | out/syntaxes: 40 | mkdir -p $@ 41 | 42 | out/syntaxes/%.json: syntaxes/%.tmlanguage.yaml out/syntaxes 43 | npx js-yaml $< > $@ 44 | 45 | syntax: out/syntaxes/tcl.json 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tcl for Visual Studio Code 2 | 3 | - Language service 4 | - Syntax highlighting 5 | - Snippets 6 | 7 | If you run into any problems, please file an issue on [GitHub](https://github.com/bitwisecook/vscode-tcl). 8 | 9 | ## Syntax highlighting 10 | ![Syntax highlighting](images/syntax-highlight.png) 11 | 12 | ## Snippets 13 | ![Snippets](images/snippets.gif) 14 | 15 | ## Code Formatting 16 | A simple code formatter 17 | 18 | ## Tcl Helpers 19 | Added the "Tcl: Escape Selections to Tcl Quoted String" command to assist with pasting data into iRules. 20 | -------------------------------------------------------------------------------- /images/Tcl-powered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwisecook/vscode-tcl/1a6fe21a778e0a611ee01e479105cfe054f2cfdf/images/Tcl-powered.png -------------------------------------------------------------------------------- /images/snippets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwisecook/vscode-tcl/1a6fe21a778e0a611ee01e479105cfe054f2cfdf/images/snippets.gif -------------------------------------------------------------------------------- /images/syntax-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwisecook/vscode-tcl/1a6fe21a778e0a611ee01e479105cfe054f2cfdf/images/syntax-highlight.png -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "documentOnTypeFormattingProvider": { 4 | "firstTriggerCharacter": "}", 5 | "moreTriggerCharacter": [ 6 | ";", 7 | "," 8 | ] 9 | }, 10 | "documentRangeFormattingProvider": "true", 11 | "renameProvider": "true", 12 | "documentHighlightProvider": "true", 13 | "referencesProvider": "true" 14 | }, 15 | "comments": { 16 | // symbol used for single line comment. Remove this entry if your language does not support line comments 17 | "lineComment": "#" 18 | }, 19 | // symbols used as brackets 20 | "brackets": [ 21 | [ 22 | "{", 23 | "}" 24 | ], 25 | [ 26 | "[", 27 | "]" 28 | ], 29 | [ 30 | "(", 31 | ")" 32 | ] 33 | ], 34 | // symbols that are auto closed when typing 35 | "autoClosingPairs": [ 36 | { 37 | "open": "{", 38 | "close": "}" 39 | }, 40 | { 41 | "open": "[", 42 | "close": "]" 43 | }, 44 | { 45 | "open": "(", 46 | "close": ")" 47 | }, 48 | { 49 | "open": "\"", 50 | "close": "\"", 51 | "notIn": [ 52 | "string" 53 | ] 54 | } 55 | ], 56 | // symbols that that can be used to surround a selection 57 | "surroundingPairs": [ 58 | [ 59 | "{", 60 | "}" 61 | ], 62 | [ 63 | "[", 64 | "]" 65 | ], 66 | [ 67 | "(", 68 | ")" 69 | ], 70 | [ 71 | "\"", 72 | "\"" 73 | ] 74 | ] 75 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tcl", 3 | "displayName": "Tcl", 4 | "description": "Tcl language support", 5 | "version": "0.4.2", 6 | "keywords": [ 7 | "tcl", 8 | "tk" 9 | ], 10 | "publisher": "bitwisecook", 11 | "bugs": { 12 | "url": "https://github.com/bitwisecook/tcl.git/LICENSE" 13 | }, 14 | "license": "MIT", 15 | "icon": "images/Tcl-powered.png", 16 | "engines": { 17 | "vscode": "^1.75.1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/bitwisecook/tcl.git" 22 | }, 23 | "categories": [ 24 | "Formatters", 25 | "Other", 26 | "Programming Languages", 27 | "Snippets" 28 | ], 29 | "activationEvents": [ 30 | "onLanguage:tcl" 31 | ], 32 | "main": "./out/extension.js", 33 | "contributes": { 34 | "languages": [ 35 | { 36 | "id": "tcl", 37 | "aliases": [ 38 | "Tcl", 39 | "tcl" 40 | ], 41 | "extensions": [ 42 | ".tcl", 43 | ".tm", 44 | ".test" 45 | ], 46 | "configuration": "./language-configuration.json" 47 | } 48 | ], 49 | "commands": [ 50 | { 51 | "command": "tcl.escapeToQuotedTcl", 52 | "title": "Escape Selections to Tcl Quoted String", 53 | "category": "Tcl" 54 | } 55 | ], 56 | "grammars": [ 57 | { 58 | "language": "tcl", 59 | "scopeName": "source.tcl", 60 | "path": "./out/syntaxes/tcl.json" 61 | } 62 | ], 63 | "snippets": [ 64 | { 65 | "language": "tcl", 66 | "path": "./snippets/tcl.json" 67 | } 68 | ] 69 | }, 70 | "scripts": { 71 | "vscode:prepublish": "webpack --mode production && npx js-yaml syntaxes/tcl.tmlanguage.yaml > out/syntaxes/tcl.json", 72 | "webpack": "webpack --mode development && npx js-yaml syntaxes/tcl.tmlanguage.yaml > out/syntaxes/tcl.json", 73 | "webpack-dev": "webpack --mode development --watch && npx js-yaml syntaxes/tcl.tmlanguage.yaml > out/syntaxes/tcl.json", 74 | "test-compile": "tsc -p ./ && npx js-yaml syntaxes/tcl.tmlanguage.yaml > out/syntaxes/tcl.json", 75 | "watch": "tsc -watch -p ./", 76 | "lint": "eslint src --ext ts", 77 | "pretest": "npm run compile && npm run lint", 78 | "test": "node ./out/test/runTest.js" 79 | }, 80 | "devDependencies": { 81 | "@types/glob": "^8.0.1", 82 | "@types/mocha": "^10.0.1", 83 | "@types/node": "^18.14.0", 84 | "@types/vscode": "^1.75.1", 85 | "@typescript-eslint/eslint-plugin": "^5.52.0", 86 | "@typescript-eslint/parser": "^5.52.0", 87 | "@vscode/test-electron": "^2.2.3", 88 | "eslint": "^8.34.0", 89 | "eslint-config-prettier": "^8.6.0", 90 | "glob": "^8.1.0", 91 | "mocha": "^10.2.0", 92 | "prettier": "^2.8.4", 93 | "ts-loader": "^9.4.2", 94 | "typescript": "^4.9.5", 95 | "vsce": "^2.15.0", 96 | "webpack": "^5.76.0", 97 | "webpack-cli": "^5.0.1" 98 | }, 99 | "dependencies": { 100 | "js-yaml": "^4.1.0" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /snippets/tcl.json: -------------------------------------------------------------------------------- 1 | { 2 | "for": { 3 | "prefix": "for", 4 | "body": [ 5 | "for {set ${1:index} ${2:0}} {\\$${1:index} < ${3:length}} {incr ${1:index}} {", 6 | "\t$0", 7 | "}" 8 | ], 9 | "description": "For Loop" 10 | }, 11 | "foreach": { 12 | "prefix": "foreach", 13 | "body": [ 14 | "foreach ${1:var} ${2:list} {", 15 | "\t$0", 16 | "}" 17 | ], 18 | "description": "Foreach Loop" 19 | }, 20 | "if": { 21 | "prefix": "if", 22 | "body": [ 23 | "if {${1:var}} {", 24 | "\t$0", 25 | "}" 26 | ], 27 | "description": "If Condition" 28 | }, 29 | "elseif": { 30 | "prefix": "elseif", 31 | "body": [ 32 | "elseif {${1:var}} {", 33 | "\t$0", 34 | "}" 35 | ], 36 | "description": "ElseIf Condition" 37 | }, 38 | "else": { 39 | "prefix": "else", 40 | "body": [ 41 | "else {", 42 | "\t$0", 43 | "}" 44 | ], 45 | "description": "Else Block" 46 | }, 47 | "proc": { 48 | "prefix": "proc", 49 | "body": [ 50 | "proc ${1:name} {${2:args}} {", 51 | "\t$0", 52 | "}" 53 | ], 54 | "description": "Proc Block" 55 | }, 56 | "while": { 57 | "prefix": "while", 58 | "body": [ 59 | "while {${1:var}} {", 60 | "\t$0", 61 | "}" 62 | ], 63 | "description": "While Loop" 64 | }, 65 | "catch": { 66 | "prefix": "catch", 67 | "body": [ 68 | "catch {${1:body}} ${2:var}" 69 | ], 70 | "description": "Catch Block" 71 | }, 72 | "try": { 73 | "prefix": "try", 74 | "body": [ 75 | "try {", 76 | "\t$1", 77 | "} finally {", 78 | "\t$0", 79 | "}" 80 | ], 81 | "description": "Try Block" 82 | }, 83 | "switch": { 84 | "prefix": "switch", 85 | "body": [ 86 | "switch ${1:var} {", 87 | "\t${2:case} {$3}", 88 | "\tdefault {$0}", 89 | "}" 90 | ], 91 | "description": "Switch Block" 92 | }, 93 | "oo::class create": { 94 | "prefix": "oo::class create", 95 | "body": [ 96 | "oo::class create ${1:name} {", 97 | "\t${2:superclass s}", 98 | "\tconstructor {} {", 99 | "\t\t$3", 100 | "\t}", 101 | "\tmethod ${4:m} {} {", 102 | "\t\t$0", 103 | "\t}", 104 | "}" 105 | ], 106 | "description": "Class Create" 107 | }, 108 | "tk_chooseDirectory": { 109 | "prefix": "tk_chooseDirectory", 110 | "body": [ 111 | "tk_chooseDirectory ${-initialdir dirname -mustexist boolean -title titleString}" 112 | ], 113 | "description": "Choose Directory" 114 | }, 115 | "tk_getOpenFile": { 116 | "prefix": "tk_getOpenFile", 117 | "body": [ 118 | "tk_getOpenFile -filetypes {", 119 | "\t\t{{Text Files} {.txt} }", 120 | "\t\t{{All Files} * }", 121 | "\t}" 122 | ], 123 | "description": "Open File Dialog" 124 | }, 125 | "tk_getSaveFile": { 126 | "prefix": "tk_getSaveFile", 127 | "body": [ 128 | "tk_getSaveFile -filetypes {", 129 | "\t\t{{Text Files} {.txt} }", 130 | "\t\t{{All Files} * }", 131 | "\t}" 132 | ], 133 | "description": "Save File Dialog" 134 | }, 135 | "tk_messageBox": { 136 | "prefix": "tk_messageBox", 137 | "body": [ 138 | "tk_messageBox ${-message msg}" 139 | ], 140 | "description": "Message Box" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/documentHelpers.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | 4 | export function fullDocumentRange(document: vscode.TextDocument): vscode.Range { 5 | const lastLineId = document.lineCount - 1; 6 | return new vscode.Range(0, 0, lastLineId, document.lineAt(lastLineId).text.length); 7 | } 8 | 9 | export function getIndentationStyle(options: vscode.FormattingOptions) { 10 | let tc: string, td: number; 11 | if (options.insertSpaces) { 12 | tc = ' '; 13 | td = options.tabSize; 14 | } 15 | else { 16 | tc = '\t'; 17 | td = 1; 18 | } 19 | const ts: number = options.tabSize; 20 | return { tc, td, ts }; 21 | } 22 | 23 | export function getSelectedLines(document: vscode.TextDocument, range: vscode.Range): vscode.Range { 24 | return new vscode.Range(document.lineAt(range.start.line).range.start, document.lineAt(range.end.line).range.end); 25 | } 26 | 27 | export function getPreviousLineContaintingText(document: vscode.TextDocument, selectedLines: vscode.Range) { 28 | if (selectedLines.start.line > 0) { 29 | let priorLineNum: number = selectedLines.start.line; 30 | let priorLine: vscode.TextLine = document.lineAt(--priorLineNum); 31 | while (priorLine.text.trim() === '' && priorLineNum > 0) { 32 | priorLine = document.lineAt(--priorLineNum); 33 | } 34 | return priorLine; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as vscode from "vscode"; 3 | import * as dochelp from "./documentHelpers"; 4 | import * as format from "./formatProvider"; 5 | 6 | export function activate(context: vscode.ExtensionContext) { 7 | vscode.languages.registerDocumentFormattingEditProvider("tcl", { 8 | provideDocumentFormattingEdits( 9 | document: vscode.TextDocument, 10 | options: vscode.FormattingOptions 11 | ): vscode.TextEdit[] { 12 | const { 13 | tc, 14 | td, 15 | ts 16 | }: { 17 | tc: string; 18 | td: number; 19 | ts: number; 20 | } = dochelp.getIndentationStyle(options); 21 | 22 | const editor = vscode.window.activeTextEditor; 23 | if (!editor) { 24 | return []; // No open text editor 25 | } 26 | return [ 27 | vscode.TextEdit.replace( 28 | dochelp.fullDocumentRange(document), 29 | format.formatTcl(document.getText(), "", tc, td) 30 | ) 31 | ]; 32 | } 33 | }); 34 | 35 | vscode.languages.registerDocumentRangeFormattingEditProvider("tcl", { 36 | provideDocumentRangeFormattingEdits( 37 | document: vscode.TextDocument, 38 | range: vscode.Range, 39 | options: vscode.FormattingOptions 40 | ): vscode.TextEdit[] { 41 | const { 42 | tc, 43 | td, 44 | ts 45 | }: { 46 | tc: string; 47 | td: number; 48 | ts: number; 49 | } = dochelp.getIndentationStyle(options); 50 | 51 | const editor = vscode.window.activeTextEditor; 52 | if (!editor) { 53 | return []; // No open text editor 54 | } 55 | 56 | let preIndent = ""; 57 | let priorLine = dochelp.getPreviousLineContaintingText( 58 | document, 59 | range 60 | ); 61 | if (priorLine !== undefined) { 62 | preIndent = format.guessPreIndentation(priorLine, tc, td, ts); 63 | } 64 | let selectedLines = dochelp.getSelectedLines(document, range); 65 | return [ 66 | vscode.TextEdit.replace( 67 | selectedLines, 68 | format.formatTcl( 69 | document.getText(selectedLines), 70 | preIndent, 71 | tc, 72 | td 73 | ) 74 | ) 75 | ]; 76 | } 77 | }); 78 | 79 | context.subscriptions.push(vscode.commands.registerCommand("tcl.escapeToQuotedTcl", _ => { 80 | const editor = vscode.window.activeTextEditor; 81 | if (!editor) { 82 | return; // No open text editor 83 | } 84 | 85 | const selections: vscode.Selection[] = [...editor.selections]; 86 | 87 | editor.edit(builder => { 88 | for (const selection of selections) { 89 | builder.replace(selection, format.escapeToQuotedTcl(editor.document.getText(selection))); 90 | } 91 | }); 92 | })); 93 | } 94 | 95 | // this method is called when your extension is deactivated 96 | export function deactivate() { } 97 | -------------------------------------------------------------------------------- /src/formatProvider.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | 4 | export function escapeToQuotedTcl(input: string): string { 5 | return input 6 | .replace(/\\/g, '\\\\') 7 | .replace(/\$/g, '\\$') 8 | .replace(/\[/g, '\\[') 9 | .replace(/\]/g, '\\]') 10 | .replace(/"/g, '\\"') 11 | .replace(/\t/g, '\\t') 12 | .replace(/\r/g, '\\r') 13 | .replace(/\n/g, '\\n') 14 | .replace(/\x0b/g, '\\v') 15 | .replace(/\x07/g, '\\a') 16 | .replace(/\x08/g, '\\b') 17 | .replace(/\x0c/g, '\\f') 18 | ; 19 | } 20 | 21 | export function guessPreIndentation(priorLine: vscode.TextLine, tabChar: string, tabDepth: number, tabSize: number) { 22 | let whiteSpace: string = priorLine.text.substr(0, priorLine.firstNonWhitespaceCharacterIndex); 23 | let preIndent = 0; 24 | let preIndentRemainder = 0; 25 | if (tabChar === ' ') { 26 | preIndent = Math.floor(whiteSpace.replace(/\t/g, ' '.repeat(tabSize)).length / tabSize) * tabDepth; 27 | preIndentRemainder = whiteSpace.replace(/\t/g, ' '.repeat(tabSize)).length % tabSize; 28 | } else { 29 | preIndent = whiteSpace.replace(new RegExp(' '.repeat(tabSize), 'g'), '\t').length; 30 | } 31 | if (/^#/.test(priorLine.text.trim())) { 32 | // do nothing 33 | } else if (/\{$/.test(priorLine.text.trim())) { 34 | preIndent += tabDepth; 35 | } else if (/\{\s*\}$/.test(priorLine.text.trim())) { 36 | // do nothing 37 | } else if (/\}$/.test(priorLine.text.trim()) && preIndent > 0) { 38 | preIndent -= tabDepth; 39 | } 40 | return tabChar.repeat(preIndent) + ' '.repeat(preIndentRemainder); 41 | } 42 | 43 | export function formatTcl(inputCode: string, preIndent: string = '', tabChar: string = ' ', tabDepth: number = 4): string { 44 | let tabLevel = 0; 45 | let out: string[] = []; 46 | let continuation = false; 47 | let uglyif2 = false; 48 | 49 | inputCode.split('\n').forEach(element => { 50 | let line = element.trim(); 51 | if (line === '') { 52 | out.push(''); 53 | } else if (/^#/.test(line)) { 54 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 55 | } else if (/\b\{\s*\}$/.test(line)) { 56 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 57 | } else if (/\{$/.test(line) || /^\{$/.test(line)) { 58 | if (/^\}/.test(line)) { 59 | tabLevel -= tabDepth; 60 | } 61 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 62 | // second part of fixing UglyIf2 63 | if (!uglyif2) { 64 | tabLevel += tabDepth; 65 | } else { 66 | uglyif2 = false; 67 | } 68 | } else if (/^\}$/.test(line)) { 69 | tabLevel -= tabDepth; 70 | if (tabLevel < 0) { 71 | tabLevel = 0; 72 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 73 | } 74 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 75 | } else if (!continuation && /^\}[^\{]+\{[^\}]+\}\s*\{[^\}]+\}$/.test(line)) { 76 | // this is a specific patch for UglyIf 77 | tabLevel -= tabDepth; 78 | if (tabLevel < 0) { 79 | tabLevel = 0; 80 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 81 | } 82 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 83 | } else if (!continuation && /^\}[^\]]+\]$/.test(line)) { 84 | // this is a specific patch for UglyStringMap 85 | tabLevel -= tabDepth; 86 | if (tabLevel < 0) { 87 | tabLevel = 0; 88 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 89 | } 90 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 91 | } else if (!continuation && /\w+\s+\{[^\{\}]+$/.test(line)) { 92 | // this is a specific patch for UglyIf2 93 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 94 | tabLevel += tabDepth; 95 | uglyif2 = true; 96 | } else if (!continuation && /\\$/.test(line)) { 97 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 98 | tabLevel += tabDepth; 99 | continuation = true; 100 | } else if (continuation && /\{\s+\\$/.test(line)) { 101 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 102 | tabLevel += tabDepth; 103 | } else if (continuation && /\[[^\t {\[["()\]}]+\s+\\$/.test(line)) { 104 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 105 | tabLevel += tabDepth; 106 | } else if (continuation && /^\\?[\]})]\s*\\$/.test(line)) { 107 | tabLevel -= tabDepth; 108 | if (tabLevel < 0) { 109 | tabLevel = 0; 110 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 111 | } 112 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 113 | } else if (continuation && (/^\\?[\]})"]$/.test(line))) { 114 | tabLevel -= tabDepth; 115 | if (tabLevel < 0) { 116 | tabLevel = 0; 117 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 118 | } 119 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 120 | tabLevel -= tabDepth; 121 | continuation = false; 122 | } else if (continuation && (/\\?[\]})"]$/.test(line))) { 123 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 124 | tabLevel -= tabDepth; 125 | continuation = false; 126 | } else if (continuation && /\\$/.test(line)) { 127 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 128 | } else if (continuation && !(/\\$/.test(line))) { 129 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 130 | tabLevel -= tabDepth; 131 | continuation = false; 132 | } else { 133 | out.push(preIndent + tabChar.repeat(tabLevel) + line); 134 | } 135 | if (tabLevel < 0) { 136 | tabLevel = 0; 137 | preIndent = preIndent.substr(tabDepth, preIndent.length - tabDepth); 138 | } 139 | }); 140 | return out.join('\n'); 141 | } -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | // import * as vscode from 'vscode'; 12 | // import * as myExtension from '../extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | -------------------------------------------------------------------------------- /src/test/suite/formatProvider.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as fp from '../../formatProvider'; 12 | 13 | // Defines a Mocha test suite to group tests of similar kind together 14 | suite("Format Tests", function () { 15 | 16 | // Defines a Mocha unit test 17 | test("Single Line", function () { 18 | const src: string = ` 19 | set a 1 20 | `; 21 | const dst: string = ` 22 | set a 1 23 | `; 24 | const preIndent = ''; 25 | const tabChar = ' '; 26 | const tabDepth = 4; 27 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 28 | }); 29 | 30 | test("Simple If", function () { 31 | const src: string = ` 32 | if {$a eq "a"} { 33 | set a 1 34 | } 35 | `; 36 | const dst: string = ` 37 | if {$a eq "a"} { 38 | set a 1 39 | } 40 | `; 41 | const preIndent = ''; 42 | const tabChar = ' '; 43 | const tabDepth = 4; 44 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 45 | }); 46 | 47 | test("Simple IfElse", function () { 48 | const src: string = ` 49 | if {$a eq "a"} { 50 | set a 1 51 | } else { 52 | set b 2 53 | } 54 | `; 55 | const dst: string = ` 56 | if {$a eq "a"} { 57 | set a 1 58 | } else { 59 | set b 2 60 | } 61 | `; 62 | const preIndent = ''; 63 | const tabChar = ' '; 64 | const tabDepth = 4; 65 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 66 | }); 67 | 68 | // this form of if/elseif/else is valid in iRules 69 | // but not valid Tcl 70 | // I'm not convinced I should fix 71 | test("UglyIf", function () { 72 | const src: string = ` 73 | proc tmp {a b} { 74 | if {$a} { 75 | puts foo 76 | } elseif {$b} { puts bar } 77 | else { puts baz } 78 | } 79 | `; 80 | const dst: string = ` 81 | proc tmp {a b} { 82 | if {$a} { 83 | puts foo 84 | } elseif {$b} { puts bar } 85 | else { puts baz } 86 | } 87 | `; 88 | const preIndent = ''; 89 | const tabChar = ' '; 90 | const tabDepth = 4; 91 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 92 | }); 93 | 94 | test("UglyStringMap", function () { 95 | const src: string = ` 96 | proc escape {value} { 97 | return [string map { 98 | "\\\\" "\\\\\\\\" 99 | "/" "\\\\/" 100 | "\\"" "\\\\\\"" 101 | } $value] 102 | } 103 | `; 104 | const dst: string = ` 105 | proc escape {value} { 106 | return [string map { 107 | "\\\\" "\\\\\\\\" 108 | "/" "\\\\/" 109 | "\\"" "\\\\\\"" 110 | } $value] 111 | } 112 | `; 113 | const preIndent = ''; 114 | const tabChar = ' '; 115 | const tabDepth = 4; 116 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 117 | }); 118 | 119 | test("UglyIf2", function () { 120 | const src: string = ` 121 | if {aaa && 122 | (bbb || ccc)} { 123 | puts "cool" 124 | } 125 | `; 126 | const dst: string = ` 127 | if {aaa && 128 | (bbb || ccc)} { 129 | puts "cool" 130 | } 131 | `; 132 | const preIndent = ''; 133 | const tabChar = ' '; 134 | const tabDepth = 4; 135 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 136 | }); 137 | 138 | test("IfElseIfElse", function () { 139 | const src: string = ` 140 | proc tmp {a b} { 141 | if {$a} { 142 | puts foo 143 | } elseif {$b} { 144 | puts bar 145 | } else { 146 | puts baz 147 | } 148 | } 149 | `; 150 | const dst: string = ` 151 | proc tmp {a b} { 152 | if {$a} { 153 | puts foo 154 | } elseif {$b} { 155 | puts bar 156 | } else { 157 | puts baz 158 | } 159 | } 160 | `; 161 | const preIndent = ''; 162 | const tabChar = ' '; 163 | const tabDepth = 4; 164 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 165 | }); 166 | 167 | test("IfElseIfElse with Comments", function () { 168 | const src: string = ` 169 | proc tmp {a b} { 170 | # my proc 171 | if {$a} { 172 | # foo 173 | puts foo 174 | } elseif {$b} { 175 | # bar 176 | puts bar 177 | } else { 178 | # baz 179 | puts baz 180 | } 181 | } 182 | `; 183 | const dst: string = ` 184 | proc tmp {a b} { 185 | # my proc 186 | if {$a} { 187 | # foo 188 | puts foo 189 | } elseif {$b} { 190 | # bar 191 | puts bar 192 | } else { 193 | # baz 194 | puts baz 195 | } 196 | } 197 | `; 198 | const preIndent = ''; 199 | const tabChar = ' '; 200 | const tabDepth = 4; 201 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 202 | }); 203 | 204 | test("RealRule1", function () { 205 | const src: string = ` 206 | when CLIENTSSL_CLIENTHELLO priority 150 { 207 | # A comment 208 | # a couple of 209 | # lines long 210 | if { [SSL::command something] } { 211 | set vs [class lookup "[string tolower [string trimright [string range [SSL::extensions -type 0] 9 end] .]] [IP::local_addr] [TCP::local_port]" something] 212 | if { \$vs eq "" } { 213 | if {[SSL::command] > 0} { 214 | binary scan [SSL::command] H* ssl_thing 215 | } else { 216 | set ssl_thing "" 217 | } 218 | HSL::send [HSL::open -proto TCP -pool "/Common/loggingservers"] "..." 219 | event disable all 220 | reject 221 | } else { 222 | virtual \$vs 223 | } 224 | } else { 225 | # anothe comment 226 | # goes here 227 | if {[SSL::command] > 0} { 228 | binary scan [SSL::command] H* ssl_thing 229 | } else { 230 | set ssl_thing "" 231 | } 232 | HSL::send [HSL::open -proto TCP -pool "/Common/loggingservers"] "..." 233 | event disable all 234 | reject 235 | } 236 | unset -nocomplain -- vs 237 | } 238 | `; 239 | const dst: string = ` 240 | when CLIENTSSL_CLIENTHELLO priority 150 { 241 | # A comment 242 | # a couple of 243 | # lines long 244 | if { [SSL::command something] } { 245 | set vs [class lookup "[string tolower [string trimright [string range [SSL::extensions -type 0] 9 end] .]] [IP::local_addr] [TCP::local_port]" something] 246 | if { \$vs eq "" } { 247 | if {[SSL::command] > 0} { 248 | binary scan [SSL::command] H* ssl_thing 249 | } else { 250 | set ssl_thing "" 251 | } 252 | HSL::send [HSL::open -proto TCP -pool "/Common/loggingservers"] "..." 253 | event disable all 254 | reject 255 | } else { 256 | virtual \$vs 257 | } 258 | } else { 259 | # anothe comment 260 | # goes here 261 | if {[SSL::command] > 0} { 262 | binary scan [SSL::command] H* ssl_thing 263 | } else { 264 | set ssl_thing "" 265 | } 266 | HSL::send [HSL::open -proto TCP -pool "/Common/loggingservers"] "..." 267 | event disable all 268 | reject 269 | } 270 | unset -nocomplain -- vs 271 | } 272 | `; 273 | const preIndent = ''; 274 | const tabChar = ' '; 275 | const tabDepth = 4; 276 | assert.equal(fp.formatTcl(src, preIndent, tabChar, tabDepth), dst); 277 | }); 278 | 279 | }); 280 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /syntaxes/tcl.tmlanguage.yaml: -------------------------------------------------------------------------------- 1 | fileTypes: 2 | - tcl 3 | - tm 4 | - tk 5 | scopeName: source.tcl 6 | firstLineMatch: '^#!/.*\b(tclsh|wish)\b([8-9]|[8-9]\.\d+)?' 7 | foldingStartMarker: '([{(\[])' 8 | foldingEndMarker: '([})\]])' 9 | name: Tcl 10 | patterns: 11 | - 12 | include: '#root' 13 | repository: 14 | root: 15 | patterns: 16 | - 17 | include: '#invariant' 18 | - 19 | include: '#brackets' 20 | - 21 | include: '#proc-call' 22 | invariant: 23 | patterns: 24 | - 25 | include: '#ignore-long-lines' 26 | - 27 | include: '#no-starting-empty-brackets' 28 | - 29 | include: '#no-empty-square-brackets' 30 | no-starting-empty-brackets: 31 | patterns: 32 | - 33 | match: '(?<=^)\s*+(\{\}|\[\])' 34 | captures: 35 | '1': 36 | name: invalid.illegal.termination.tcl 37 | no-empty-square-brackets: 38 | patterns: 39 | - 40 | match: '(\[\])' 41 | captures: 42 | '1': 43 | name: string.empty.square.brace 44 | ignore-long-lines: 45 | comment: 'long lines are not parsed for performance' 46 | patterns: 47 | - 48 | match: '^.{1000,}' 49 | brackets: 50 | patterns: 51 | - 52 | include: '#invariant' 53 | - 54 | include: '#special-brackets' 55 | - 56 | include: '#empty-brackets' 57 | - 58 | include: '#square-brackets' 59 | - 60 | include: '#curly-brackets' 61 | square-brackets: 62 | patterns: 63 | - 64 | begin: '\s*+(?])' 677 | name: support.type.expression.operator.tcl 678 | - 679 | comment: 'expression bitwise' 680 | match: '([|^&!~])' 681 | name: support.type.expression.operator.tcl 682 | - 683 | comment: 'expression ternary' 684 | match: '([?:])' 685 | name: support.type.expression.operator.tcl 686 | expression-functions: 687 | patterns: 688 | - 689 | begin: \b(abs|acos|asin|atan|atan2|bool|ceil|cos|cosh|double|entier|exp|floor|fmod|hypot|int|isqrt|log|log10|max|min|pow|rand|round|sin|sinh|sqrt|srand|tan|tanh|wide)\b(\() 690 | end: (\)) 691 | beginCaptures: 692 | '1': 693 | name: entity.function.name.math.function.punctuatio.italic.tcl 694 | '2': 695 | name: entity.function.name.math.function.brace.open.tcl 696 | endCaptures: 697 | '1': 698 | name: entity.function.name.math.function.brace.close.tcl 699 | patterns: 700 | - 701 | include: '#expressions' 702 | tcl-vwait: 703 | patterns: 704 | - 705 | include: '#line-escape' 706 | - 707 | comment: 'vwait varName' 708 | begin: "(?<=^|\\[|{|;)\\s*(::)?\\b(vwait)\\b\\s*+(?!$|\n|;|^|\\]|})" 709 | end: "(?=$|\n|;|\\]|}|\\s)\\s*([^$\n^\\];}\\[\\\\]*)?(\\\\(?!\n))?" 710 | beginCaptures: 711 | '1': 712 | name: support.type.ns.tcl 713 | '2': 714 | name: keyword.control.vwait.tcl 715 | endCaptures: 716 | '1': 717 | name: invalid.illegal.numargs.tcl 718 | '2': 719 | name: invalid.illegal.escape.tcl 720 | patterns: 721 | - 722 | include: '#line-escape' 723 | - 724 | include: '#variable' 725 | - 726 | include: '#variable-bare-italic' 727 | tcl-proc: 728 | patterns: 729 | - 730 | begin: "(?<=^|[\\[{;])\\s*(::)?\\b(proc)\\b\\s+([^\\s\n]+)\\s+(?={|\\b|\\[|\"|\\\\$)" 731 | end: '(?=(?|=|==|<=|>=|>_|_<|%))(?=\s*\|)' 1413 | name: support.type 1414 | - 1415 | begin: '\s*+(\|)' 1416 | end: "(?=$|;|\n|}|\\])" 1417 | beginCaptures: 1418 | '1': 1419 | name: entity.function.name.condition.tcl 1420 | name: string 1421 | patterns: 1422 | - 1423 | include: '#variable-non-substituting' 1424 | - 1425 | include: '#glob-match' 1426 | - 1427 | include: '#constant-numeric' 1428 | tcl-state-setter-getter: 1429 | patterns: 1430 | - 1431 | begin: '(?<=set|get|pull|push)\b' 1432 | end: "(?=(?)' 2658 | captures: 2659 | '1': 2660 | name: meta.constant 2661 | - 2662 | include: '#variable' 2663 | - 2664 | include: '#square-brackets' 2665 | - 2666 | include: '#variable-bare' 2667 | - 2668 | begin: '\s*({)' 2669 | end: '(})' 2670 | patterns: 2671 | - 2672 | match: '(?<={)\s*([^\s}]*)' 2673 | name: string.pull.prop.tcl 2674 | - 2675 | begin: '(?<=\s)([^\s}]*)' 2676 | end: '(?=})' 2677 | captures: 2678 | '1': 2679 | patterns: 2680 | - 2681 | include: '#variable-bare-italic' 2682 | patterns: 2683 | - 2684 | include: '#constant-numeric' 2685 | - 2686 | begin: '\s*({)' 2687 | end: '(})' 2688 | contentName: string.dict.pull.default.tcl 2689 | captures: 2690 | '1': 2691 | name: entity.function.name.dict.pull.props.tcl 2692 | - 2693 | include: '#bare-string' 2694 | - 2695 | include: '#constant-numeric' 2696 | - 2697 | begin: '(?<=set|unset|lappend|incr)\b' 2698 | end: "(?=(?)' 2921 | name: meta.constant.re.trashvar.tcl 2922 | - 2923 | include: '#opt' 2924 | - 2925 | begin: '\s*({|")' 2926 | end: "(}|\")(?=\\s+(?:\\\\\n|$|\n|\\[|\\$|\\b|\"|;|}|-))" 2927 | contentName: string.tcl 2928 | patterns: 2929 | - 2930 | include: '#regular-expression' 2931 | - 2932 | include: '#quoted-string' 2933 | - 2934 | include: '#variable' 2935 | - 2936 | include: '#variable-bare-italic' 2937 | - 2938 | begin: '(?<=_regexp|_re)\s+(?={|")' 2939 | end: '(?=(?)|(@|}|$|\n)|\\|" 3006 | beginCaptures: 3007 | '1': 3008 | name: support.type.annotate.tcl 3009 | endCaptures: 3010 | '1': 3011 | name: support.type.annotate.tcl 3012 | patterns: 3013 | - 3014 | include: '#variable-non-substituting' 3015 | - 3016 | include: '#namespace-separator' 3017 | annotate-properties: 3018 | begin: "(?=\\s*@[^\\s\n])" 3019 | end: "(?=$|[\n}@])" 3020 | patterns: 3021 | - 3022 | comment: 'special @ values that are meant as property values' 3023 | match: '\s*+(@)\b(type)\b(\s+[^{}|@\[<>-]*)?' 3024 | captures: 3025 | '1': 3026 | name: meta.constant.annotate.property.tcl 3027 | '2': 3028 | name: support.type.name.tcl 3029 | '3': 3030 | name: entity.function.property.name.tcl 3031 | - 3032 | comment: 'special @ values that are meant as property values' 3033 | match: '\s*+(@)\b(arg(?:s)?|returns|key|prop|if|example|param|params|opt|option|argument|val|value)\b(\s+[^\/{}|@\[<>]*)?' 3034 | captures: 3035 | '1': 3036 | name: support.type.annotate.property.tcl 3037 | '2': 3038 | name: variable.property.name.tcl 3039 | '3': 3040 | name: support.type 3041 | patterns: 3042 | - 3043 | include: '#opt' 3044 | - 3045 | comment: '@ (no space after @)' 3046 | match: '\s*+(@)\b([^\s]*)\b(\s+[^\/{}|@\[<>-]*)?' 3047 | captures: 3048 | '1': 3049 | name: meta.constant.annotate.property.tcl 3050 | '2': 3051 | name: entity.function.property.name.tcl 3052 | '3': 3053 | name: support.type.tcl 3054 | - 3055 | include: '#annotate' 3056 | annotate-control-block: 3057 | patterns: 3058 | - 3059 | comment: 'bracketed within a comment' 3060 | contentName: keyword.operator 3061 | begin: '^\s*+(?={)' 3062 | end: '(?<=})' 3063 | patterns: 3064 | - 3065 | include: '#brackets' 3066 | annotate-proc-call: 3067 | patterns: 3068 | - 3069 | begin: '(\[)' 3070 | end: '(\])' 3071 | beginCaptures: 3072 | '1': 3073 | name: support.type.annotate.proc-call.tcl 3074 | endCaptures: 3075 | '1': 3076 | name: support.type.annotate.proc-call.tcl 3077 | patterns: 3078 | - 3079 | include: '#proc-call' 3080 | annotate-type: 3081 | patterns: 3082 | - 3083 | comment: 'adding @ in a multi-line will make the line emphasized' 3084 | contentName: entity.function.name.comment.annotate.tcl 3085 | begin: '\s*+({)' 3086 | end: '\s*(})' 3087 | beginCaptures: 3088 | '1': 3089 | name: meta.constant.comment.annotate.tcl 3090 | endCaptures: 3091 | '1': 3092 | name: meta.constant.comment.annotate.tcl 3093 | patterns: 3094 | - 3095 | include: '#annotate-regular-expression' 3096 | - 3097 | include: '#annotate-proc-call' 3098 | - 3099 | begin: (<) 3100 | end: '(>)|(?=[}])' 3101 | contentName: support.type.comment.annotate.property.punctuation.italic.tcl 3102 | patterns: 3103 | - 3104 | include: '#annotate-regular-expression' 3105 | - 3106 | match: (\|) 3107 | name: entity.function.name.tcl 3108 | - 3109 | include: '#variable-non-substituting' 3110 | - 3111 | match: '([,])' 3112 | name: entity.function.name.annotate.opt.tcl 3113 | - 3114 | match: '([-])' 3115 | name: meta.constant.annotate.opt.tcl 3116 | - 3117 | match: '(?:(?<={|\|)([?*])|([?*])(?=}|\|))' 3118 | name: variable.modifier.annotate.punctuation.italic.tcl 3119 | - 3120 | match: (\|) 3121 | name: meta.constant.comment.annotate.tcl 3122 | annotate-regular-expression: 3123 | patterns: 3124 | - 3125 | begin: (/) 3126 | end: (/|$) 3127 | beginCaptures: 3128 | '1': 3129 | name: support.type.annotate.re.brace.open.tcl 3130 | endCaptures: 3131 | '1': 3132 | name: support.type.annotate.re.brace.close.tcl 3133 | contentName: string.re.expression.comment.annotate.contents.tcl 3134 | patterns: 3135 | - 3136 | include: '#regular-expression' 3137 | annotate-highlight: 3138 | patterns: 3139 | - 3140 | comment: 'adding | in a multi-line will make the line highlighted (string)' 3141 | match: '(?<=\|)\s*([^|]*)' 3142 | captures: 3143 | '1': 3144 | name: string.quote.annotate.comment.tcl 3145 | patterns: 3146 | - 3147 | include: '#annotate-type' 3148 | - 3149 | include: '#annotate-proc-call' 3150 | - 3151 | include: '#variable-non-substituting' 3152 | - 3153 | match: '\b(http://([^\s]*))' 3154 | name: entity.function.name.markup.underline 3155 | annotate-header: 3156 | patterns: 3157 | - 3158 | comment: 'adding > in a multi-line will make the line emphasized' 3159 | begin: '\s*+(>)' 3160 | end: '(<)|(?=$|>)' 3161 | contentName: meta.constant.annotate.header.tcl 3162 | captures: 3163 | '1': 3164 | name: entity.function.name.annotate.tcl 3165 | '2': 3166 | name: meta.constant.annotate.header.tcl 3167 | patterns: 3168 | - 3169 | include: '#annotate' 3170 | annotate-list: 3171 | patterns: 3172 | - 3173 | match: '^\s*([0-9]\.)\s+' 3174 | name: meta.class 3175 | opt: 3176 | patterns: 3177 | - 3178 | comment: 'Handle options sent to commands at various points (cmd -name1 value -name2 value)' 3179 | match: "\\s*+(?<=\\s|^|\"|\\()((-)(?!-|\\s|\"|;|\n|>)[^\\s$\n\\[\\];\"{)(}]*)" 3180 | captures: 3181 | '1': 3182 | name: support.type.opt.tcl 3183 | '2': 3184 | name: meta.constant.opt.start.tcl 3185 | line-escape: 3186 | begin: "\\s*+(\\\\\n)$" 3187 | end: '(^)(?:\s*(?!$))?' 3188 | beginCaptures: 3189 | '1': 3190 | name: constant.character.escape.newline.tcl 3191 | endCaptures: 3192 | '1': 3193 | name: meta.lineescape.highlight.tcl 3194 | glob-match: 3195 | patterns: 3196 | - 3197 | begin: '({)' 3198 | end: '(})' 3199 | patterns: 3200 | - 3201 | include: '#glob-match' 3202 | - 3203 | match: '\s*+(\*)' 3204 | name: meta.class 3205 | - 3206 | include: '#string-escapes' 3207 | - 3208 | begin: '(?|<|,))(?\\*}\\]/\"\\);\\(\\\\\\$,\\-\\+\\*]*)(\\([^\\s\\\\;\\*}\\]\"\\)\\(\\$,\\-\\+\\*<>]*\\))?)" 3455 | captures: 3456 | '1': 3457 | name: support.type.variable.indicator.tcl 3458 | '2': 3459 | name: entity.function.name.variable.brace.open.tcl 3460 | '3': 3461 | patterns: 3462 | - 3463 | include: '#namespace-separator' 3464 | - 3465 | include: '#variable' 3466 | - 3467 | match: '([^\s:"}{]+)' 3468 | name: meta.constant.tcl 3469 | '4': 3470 | patterns: 3471 | - 3472 | begin: (\() 3473 | end: (\)) 3474 | contentName: punctuation.italic.meta.constant 3475 | beginCaptures: 3476 | '1': 3477 | name: support.type 3478 | endCaptures: 3479 | '1': 3480 | name: support.type 3481 | patterns: 3482 | - 3483 | include: '#proc-call-args' 3484 | '5': 3485 | name: entity.function.name.variable.brace.close.tcl 3486 | '6': 3487 | patterns: 3488 | - 3489 | include: '#namespace-separator' 3490 | - 3491 | match: '([^\s:"}{\\\]\[]+)' 3492 | name: meta.constant 3493 | '7': 3494 | patterns: 3495 | - 3496 | begin: (\() 3497 | end: (\)) 3498 | contentName: punctuation.italic.meta.constant 3499 | beginCaptures: 3500 | '1': 3501 | name: entity.function.name 3502 | endCaptures: 3503 | '1': 3504 | name: entity.function.name 3505 | patterns: 3506 | - 3507 | include: '#proc-call-args' 3508 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es2018" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true, /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | "noImplicitAny": false 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | ".vscode-test" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', 10 | entry: './src/extension.ts', 11 | output: { 12 | path: path.resolve(__dirname, 'out'), 13 | filename: 'extension.js', 14 | libraryTarget: 'commonjs2', 15 | devtoolModuleFilenameTemplate: '../[resource-path]' 16 | }, 17 | devtool: 'source-map', 18 | externals: { 19 | vscode: 'commonjs vscode' 20 | }, 21 | resolve: { 22 | extensions: ['.ts', '.js'] 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | exclude: /node_modules/, 29 | use: [ 30 | { 31 | loader: 'ts-loader' 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | }; 38 | module.exports = config; 39 | --------------------------------------------------------------------------------