├── .gitignore ├── custom-language-syntax-0.5.5.vsix ├── images └── customMarkdownBackticks.gif ├── .vscodeignore ├── .vscode ├── extensions.json └── launch.json ├── .markdownlint.jsonc ├── tsconfig.json ├── test ├── suite │ └── extension.test.js └── runTest.js ├── .eslintrc.json ├── LICENSE ├── exampleConfig.js_bak ├── package.json ├── src ├── languageConfigs.js ├── extension.js ├── extensionSettings.js ├── completionProviders.js └── getLanguageFiles.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | -------------------------------------------------------------------------------- /custom-language-syntax-0.5.5.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturoDent/custom-language-properties/HEAD/custom-language-syntax-0.5.5.vsix -------------------------------------------------------------------------------- /images/customMarkdownBackticks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArturoDent/custom-language-properties/HEAD/images/customMarkdownBackticks.gif -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | vsc-extension-quickstart.md 6 | **/jsconfig.json 7 | **/*.map 8 | **/.eslintrc.json 9 | CHANGELOG.md -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.markdownlint.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, // 80 line length 3 | "MD026": false, // no trailing punctation 4 | "MD028": false, // no blank line inside blockquote 5 | "MD033": false, // no in-line html 6 | "MD035": false, // horizontal rule end with ---------; 7 | "MD038": false // no spaces inside code span elements 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noEmit": true, 5 | "target": "ESNext", 6 | "checkJs": true, 7 | "allowJs": true, 8 | "alwaysStrict": true, 9 | "resolveJsonModule": true, 10 | "strict": true, 11 | "lib": [ 12 | "ESNext" 13 | ] 14 | }, 15 | "exclude": ["node_modules"], 16 | "include": ["src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /test/suite/extension.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | const vscode = require('vscode'); 6 | // const myExtension = require('../extension'); 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-const-assign": "warn", 18 | "no-this-before-super": "warn", 19 | "no-undef": "warn", 20 | "no-unreachable": "warn", 21 | "no-unused-vars": "warn", 22 | "constructor-super": "warn", 23 | "valid-typeof": "warn" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/runTest.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { runTests } = require('vscode-test'); 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 the extension test script 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 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension 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 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "${workspaceFolder}/../Test Bed", 15 | 16 | "--extensionDevelopmentPath=${workspaceFolder}" 17 | ] 18 | }, 19 | { 20 | "name": "Extension Tests", 21 | "type": "extensionHost", 22 | "request": "launch", 23 | "runtimeExecutable": "${execPath}", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/test/suite/index" 27 | ] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Arturo Dent. All rights reserved. 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 -------------------------------------------------------------------------------- /exampleConfig.js_bak: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | 3 | vscode.languages.setLanguageConfiguration( 4 | 'javascript', 5 | { 6 | wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\'\"\,\.\<\>\/\?\s]+)/g, 7 | 8 | indentationRules: { 9 | decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/, 10 | increaseIndentPattern: /^.*\{[^}"']*$/, 11 | }, 12 | 13 | comments: { 14 | lineComment: "#", 15 | blockComment: ["<#", "#>"], 16 | }, 17 | 18 | brackets: [ 19 | ["{", "}"], 20 | ["[", "]"], 21 | ["(", ")"], 22 | ], 23 | 24 | onEnterRules: [ 25 | { 26 | beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, 27 | afterText: /^\s*\*\/$/, 28 | action: { indentAction: vscode.IndentAction.IndentOutdent, appendText: " * " }, 29 | }, 30 | { 31 | beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, 32 | action: { indentAction: vscode.IndentAction.None, appendText: " * " }, 33 | }, 34 | { 35 | beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/, 36 | action: { indentAction: vscode.IndentAction.None, appendText: "* " }, 37 | }, 38 | { 39 | beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/, 40 | action: { indentAction: vscode.IndentAction.None, removeText: 1 }, 41 | }, 42 | { 43 | beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/, 44 | action: { indentAction: vscode.IndentAction.None, removeText: 1 }, 45 | }, 46 | ], 47 | }); 48 | 49 | 50 | "{\r\n \"javascript.comments.lineComment\": \"// *\",\r\n \"javascript.comments.blockComment\": [\"/*\",\"*/\"],\r\n \"javascript.\"\r\n\t\t\"cpp.brackets\": [[\"{\",\"}\"],[\"[\",\"]\"],[\"(\",\")\"],[\"<\",\">\"]]\r\n }" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-language-syntax", 3 | "displayName": "Custom Language Properties", 4 | "publisher": "ArturoDent", 5 | "description": "Create your custom language properties", 6 | "version": "0.6.6", 7 | "preview": true, 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/ArturoDent/custom-language-properties" 12 | }, 13 | "engines": { 14 | "vscode": "^1.83.0" 15 | }, 16 | "extensionKind": [ 17 | "ui", 18 | "workspace" 19 | ], 20 | "categories": [ 21 | "Other" 22 | ], 23 | "keywords": [ 24 | "custom", 25 | "language", 26 | "comments", 27 | "line comment", 28 | "block comment" 29 | ], 30 | "activationEvents": [ 31 | "onStartupFinished" 32 | ], 33 | "main": "./src/extension.js", 34 | "browser": "./src/extension.js", 35 | "contributes": { 36 | "configuration": { 37 | "title": "Custom Language Properties", 38 | "properties": { 39 | "custom-language-properties": { 40 | "type": "object", 41 | "description": "Custom language syntax, like `//* `.", 42 | "markdownDescription": "Custom language syntax, like `//* `." 43 | } 44 | } 45 | }, 46 | 47 | "commands": [ 48 | { 49 | "command": "custom-language-syntax.showConfigFile", 50 | "title": "Show language configuration for current editor", 51 | "category": "Custom Language Properties" 52 | }, 53 | { 54 | "command": "custom-language-syntax.rebuildConfigFiles", 55 | "title": "Check for new language extensions", 56 | "category": "Custom Language Properties" 57 | } 58 | ] 59 | }, 60 | "scripts": { 61 | "lint": "eslint .", 62 | "pretest": "npm run lint", 63 | "test": "node ./test/runTest.js" 64 | }, 65 | "devDependencies": { 66 | "@types/glob": "^7.1.1", 67 | "@types/mocha": "^7.0.2", 68 | "@types/node": "^13.13.39", 69 | "@types/vscode": "^1.52.0", 70 | "eslint": "^6.8.0", 71 | "glob": "^7.1.6", 72 | "mocha": "^7.1.2", 73 | "typescript": "^3.8.3", 74 | "vscode-test": "^1.3.0" 75 | }, 76 | "dependencies": { 77 | "jsonc-parser": "^3.0.0" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/languageConfigs.js: -------------------------------------------------------------------------------- 1 | // const vscode = require('vscode'); 2 | // const jsonc = require('jsonc-parser'); 3 | // const fs = require('fs'); 4 | // const path = require('path'); 5 | 6 | 7 | 8 | // /** 9 | // * Fom the language configuration for the current file 10 | // * get the value of config argument 11 | // * @param {string} langID - the languageID of the desired language configuration 12 | // * @param {string} config - the language configuration to get, e.g., 'comments.lineComment' or 'autoClosingPairs' 13 | // * @returns {any} - string or array or null if can't be found 14 | // */ 15 | // exports.get = function (langID, config) { 16 | 17 | // // const currentLanguageConfig = languageConfigs.get('javascript', 'comments'); 18 | 19 | // // if pass in no config ? 20 | // let configArg; 21 | 22 | // if (config && config.includes('.')) configArg = config.split('.'); 23 | // else configArg = config; 24 | 25 | // let desiredConfig = null; // return null default if can't be found 26 | 27 | // var langConfigFilePath = null; 28 | 29 | // for (const _ext of vscode.extensions.all) { 30 | // if ( _ext.packageJSON.contributes && _ext.packageJSON.contributes.languages ) { 31 | // // Find language data from "packageJSON.contributes.languages" for the langID argument 32 | // // don't filter if you want them all 33 | // const packageLangData = _ext.packageJSON.contributes.languages.find( 34 | // ( /** @type {{ id: string; }} */ _packageLangData) => (_packageLangData.id === langID) ); 35 | // // if found, get the absolute config file path 36 | // if (!!packageLangData) { 37 | // langConfigFilePath = path.join( _ext.extensionPath, packageLangData.configuration ); 38 | // break; 39 | // } 40 | // } 41 | // } 42 | 43 | // // "c:\\Users\\xxx\\AppData\\Local\\Programs\\Microsoft VS Code Insiders\\resources\\app\\extensions\\javascript\\javascript-language-configuration.json" 44 | // // drill down through config args 45 | // if (!!langConfigFilePath && fs.existsSync(langConfigFilePath)) { 46 | 47 | // // the whole language config will be returned if config arg was the empty string '' 48 | // desiredConfig = jsonc.parse(fs.readFileSync(langConfigFilePath).toString()); 49 | 50 | // if (Array.isArray(configArg)) { 51 | 52 | // for (let index = 0; index < configArg.length; index++) { 53 | // desiredConfig = desiredConfig[configArg[index] ]; 54 | // } 55 | // return desiredConfig; 56 | // } 57 | // // only one arg without a dot, like 'comments' passed in 58 | // else if (config) return jsonc.parse(fs.readFileSync(langConfigFilePath).toString())[config]; 59 | // else return desiredConfig; 60 | // } 61 | // else return null; 62 | // } -------------------------------------------------------------------------------- /src/extension.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const extSettings = require('./extensionSettings'); 6 | const providers = require('./completionProviders'); 7 | const makeFiles = require('./getLanguageFiles'); 8 | 9 | 10 | 11 | /** 12 | * @param {vscode.ExtensionContext} context 13 | */ 14 | async function activate(context) { 15 | 16 | const extConfigDirectory = path.join(context.globalStorageUri.fsPath, 'languageConfigs'); 17 | const extLangPropDirectory = path.join(context.globalStorageUri.fsPath, 'languageProperties'); 18 | 19 | // so first run only, doesn't mean there is anything in those directories though 20 | if (!fs.existsSync(extConfigDirectory) || !fs.existsSync(extLangPropDirectory)) { 21 | await makeFiles.getLanguageConfigFiles(context, extConfigDirectory); 22 | await makeFiles.reduceFiles(context, extConfigDirectory, extLangPropDirectory); 23 | } 24 | 25 | await extSettings.getSettingsAndSetConfigs(context); 26 | providers.makeSettingsCompletionProvider(context); 27 | 28 | // --------------------------------------------------------------------------------------------------------- 29 | 30 | let disposable = vscode.commands.registerCommand('custom-language-syntax.showConfigFile', async function () { 31 | 32 | if (vscode.window.activeTextEditor) 33 | makeFiles.showLanguageConfigFile(vscode.window.activeTextEditor.document.languageId); 34 | }); 35 | context.subscriptions.push(disposable); 36 | 37 | // --------------------------------------------------------------------------------------------------------- 38 | 39 | disposable = vscode.commands.registerCommand('custom-language-syntax.rebuildConfigFiles', async function () { 40 | 41 | const newLangs = await makeFiles.getLanguageConfigFiles(context, extConfigDirectory); 42 | 43 | await makeFiles.reduceFiles(context, extConfigDirectory, extLangPropDirectory); 44 | await extSettings.getSettingsAndSetConfigs(context); // calls _setConfig() too 45 | 46 | let message = `No new languages found.`; 47 | if (newLangs.newLanguageCount) { 48 | let langs = newLangs.newLanguages.join(', '); 49 | message = `New languages found: ${langs}.`; 50 | } 51 | vscode.window.showInformationMessage(`Finished. ${message}`); 52 | }); 53 | context.subscriptions.push(disposable); 54 | 55 | // --------------------------------------------------------------------------------------------------------- 56 | 57 | disposable = vscode.workspace.onDidChangeConfiguration(async event => { 58 | 59 | // includes removal or commenting out or change, must save to trigger 60 | if (event.affectsConfiguration('custom-language-properties')) 61 | await extSettings.getSettingsAndSetConfigs(context); 62 | 63 | context.subscriptions.push(disposable); 64 | }); 65 | } 66 | 67 | exports.activate = activate; 68 | 69 | function deactivate() {} 70 | exports.deactivate = deactivate; -------------------------------------------------------------------------------- /src/extensionSettings.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const jsonc = require('jsonc-parser'); 5 | 6 | let languagesInSettingsSet = new Set(); // currently in the settings 7 | let previousLanguagesInSettingsSet = new Set(); // previously in the settings before change to configuration 8 | let droppedLanguages = new Set(); // languages dropped on new configuration 9 | 10 | 11 | /** 12 | * Main driver: Get the current settings and update the configs for current and dropped languages. 13 | * @param {vscode.ExtensionContext} context 14 | */ 15 | exports.getSettingsAndSetConfigs = async function (context) { 16 | 17 | let settingConfigs = await _load(); 18 | 19 | // loop through all languages that appear in the 'custom-language-properties' settings 20 | // and add to the languagesInSettingsSet 21 | 22 | for (const langObject of Object.values(settingConfigs)) { 23 | 24 | const language = langObject[0].match(/^(.*?)\.(?=comments|brackets)/m); 25 | if (language) languagesInSettingsSet.add(language[1]); 26 | } 27 | 28 | // compare the previousLanguagesInSettingsSet to languagesInSettingsSet 29 | // updates droppedLanguages Set and sets their configs to the default state 30 | _updateDroppedLanguages(settingConfigs, context); 31 | previousLanguagesInSettingsSet = new Set(languagesInSettingsSet); 32 | 33 | languagesInSettingsSet.forEach(async currentLang => 34 | await _setConfig(settingConfigs, context, new Set([currentLang]))); 35 | } 36 | 37 | /** 38 | * Get this extension's 'custom-language-properties' settings 39 | * @returns - a vscode.WorkspaceConfiguration 40 | */ 41 | async function _load() { 42 | console.log(); 43 | const configs = vscode.workspace.getConfiguration('custom-language-properties'); 44 | // to strip off WorkspaceConfiguration.has/inspect/etc. 45 | return Object.entries(configs).filter(config => typeof config[1] !== 'function'); 46 | }; 47 | 48 | 49 | /** 50 | * Set the configs for any languages dropped from the settings configuration. 51 | * @param {[string, any][]} settingConfigs 52 | * @param {vscode.ExtensionContext} context 53 | */ 54 | async function _updateDroppedLanguages (settingConfigs, context) { 55 | 56 | droppedLanguages.clear(); 57 | previousLanguagesInSettingsSet.forEach(prevLang => { 58 | if (!languagesInSettingsSet.has(prevLang)) droppedLanguages.add(prevLang); 59 | }); 60 | 61 | if (droppedLanguages.size) await _setConfig(settingConfigs, context, droppedLanguages); 62 | } 63 | 64 | 65 | /** 66 | * SetLanguageConfiguration() for the given languageID's, both current and dropped languages. 67 | * 68 | * @param {[string, any][]} settingConfigs - this extension's settings 69 | * @param {vscode.ExtensionContext} context 70 | * @param {Set} languageSet - an array of languageID's 71 | */ 72 | async function _setConfig (settingConfigs, context, languageSet) { 73 | 74 | let disposable; 75 | // const configSet = new Set(['comments', 'brackets', 'indentationRules', 'onEnterRules', 'wordPattern']); 76 | const configSet = new Set(['comments', 'brackets', 'autoClosingPairs']); 77 | //const configSet = new Set(['comments', 'brackets']); 78 | 79 | languageSet.forEach(async langID => { 80 | 81 | const originalLangID = langID; 82 | // if langID = 'html.erb' get only the 'erb' part 83 | langID = langID.replace(/^(.*\.)?(.+)$/m, '$2'); 84 | const thisPath = path.join(context.globalStorageUri.fsPath, 'languageConfigs', `${ langID }-language.json`); 85 | 86 | if (!!thisPath && fs.existsSync(thisPath)) { 87 | 88 | // this is the default language configuration 89 | let thisLanguageConfig = jsonc.parse(fs.readFileSync(thisPath).toString()); 90 | 91 | // // delete everything except comments, brackets and autoClosingPairs at present 92 | // delete everything except comments and brackets at present 93 | for (const property in thisLanguageConfig) { 94 | if (!configSet.has(property)) delete thisLanguageConfig[property]; 95 | } 96 | 97 | // The Object.entries() method returns an array of a given object's 98 | // own enumerable string-keyed property [key, value] pairs. 99 | 100 | // [ 101 | // [ 102 | // "html.erb.comments.blockComment", 103 | // [ 104 | // "", 106 | // ], 107 | // ], 108 | // [ 109 | // "bat.comments.lineComment", 110 | // "::", 111 | // ], 112 | // ] 113 | 114 | for (let index = 0; index < settingConfigs.length; index++) { 115 | 116 | let entry = settingConfigs[index]; 117 | 118 | //const re = /^(.*\.)?(?.+)\.(?=comments|brackets)(?.*)/m; 119 | const re = /^(.*\.)?(?.+)\.(?=comments|brackets|autoClosingPairs)(?.*)/m; 120 | 121 | let found = entry[0].match(re); 122 | 123 | // get 'html.erb' from 'html.erb.comments.lineComment' 124 | // let found = entry[0].match(/^(.*\.)?(?.+)\.(?=comments|brackets)/m); 125 | 126 | if (!found?.groups || found.groups?.lang !== langID) continue; 127 | 128 | let prop = found.groups?.prop; 129 | 130 | // get 'comments.lineComment' from 'html.erb.comments.lineComment' 131 | // let prop = entry[0].replace(/^(.+)\.(?=comments|brackets)(.*)$/m, '$2'); 132 | 133 | // e.g., prop = "comments.lineComment" 134 | if (prop.includes('.')) { 135 | 136 | let temp = prop.split('.'); 137 | 138 | // need to set BOTH comment:lineComment and comment:blockComment, 139 | // else it is deleted from the configuration!! 140 | // will overwrite the default config with matching entry in the settings 141 | if (temp.length === 2 && configSet.has(temp[0])) { 142 | thisLanguageConfig[temp[0]][temp[1]] = entry[1]; 143 | } 144 | } 145 | // this works except for notIn[] 146 | // else if (configSet.has(prop) && prop === 'autoClosingPairs'){ 147 | // thisLanguageConfig['autoClosingPairs'] = entry[1]; 148 | // } 149 | // prop = "brackets[[]] brackets is an array of arrays 150 | 151 | else if (configSet.has(prop)) { 152 | thisLanguageConfig[prop] = entry[1]; 153 | } 154 | } 155 | 156 | // use full `html.erb' ere 157 | disposable = vscode.languages.setLanguageConfiguration( originalLangID, thisLanguageConfig ); 158 | context.subscriptions.push(disposable); 159 | } 160 | // else couldn't set config, languageConfigs/${ langID }-language.json doesn't exist 161 | }) 162 | }; -------------------------------------------------------------------------------- /src/completionProviders.js: -------------------------------------------------------------------------------- 1 | const vscode = require('vscode'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const jsonc = require('jsonc-parser'); 5 | 6 | 7 | /** 8 | * Register a CompletionProvider for settings.json 9 | * Get either all languageIds or language configuration properties for a specific language 10 | * 11 | * @param {vscode.ExtensionContext} extensionContext 12 | * @returns a CompletionProvider 13 | */ 14 | exports.makeSettingsCompletionProvider = function(extensionContext) { 15 | const settingsCompletionProvider = vscode.languages.registerCompletionItemProvider ( 16 | { pattern: '**/settings.json' }, 17 | { 18 | // eslint-disable-next-line no-unused-vars 19 | async provideCompletionItems(document, position, token, context) { 20 | 21 | // "custom-language-properties": { 22 | // "javascript.comments.lineComment": "//", 23 | // "javascript.indentationRules.javascript": "^}}((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`}}}]*|\\[[^\\]\"'`]*)$", 24 | // "html.indentationRules.html": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`}}}]*|\\[[^\\]\"'`]*)$" 25 | // }, 26 | 27 | if (!vscode.window.activeTextEditor) return undefined; 28 | 29 | const curLocation = jsonc.getLocation(document.getText(), document.offsetAt(position)); 30 | const command = curLocation.path[0]; 31 | if (command !== 'custom-language-properties') return undefined; 32 | 33 | const linePrefix = document.lineAt(position).text.substring(0, position.character); 34 | 35 | if (curLocation.isAtPropertyKey && linePrefix.endsWith('"')) return _getCompletionItemsNewLangs(); 36 | 37 | else if (curLocation.isAtPropertyKey && linePrefix.endsWith('.')) { 38 | 39 | let language = ""; 40 | let langNode; 41 | let args = ""; 42 | let /** @type {string[]} */ found = []; 43 | 44 | // curLocation.path[1] = 'javascript.' 45 | if (curLocation.path && curLocation.path[1].toString().endsWith('.')) { 46 | language = curLocation.path[1].toString(); 47 | language = language.substring(0, language.length - 1); 48 | } 49 | else return undefined; 50 | 51 | let completionArray = _getCompletionItemsProperties(language, position, extensionContext); 52 | 53 | const rootNode = jsonc.parseTree(document.getText()); 54 | if (rootNode) langNode = jsonc.findNodeAtLocation(rootNode, ['custom-language-properties']); 55 | else return completionArray; 56 | 57 | if (langNode) { 58 | args = document.getText(new vscode.Range(document.positionAt(langNode?.offset), 59 | document.positionAt(langNode?.offset + langNode.length))); 60 | // @ts-ignore 61 | found = [...args.matchAll(/^\s*"(?[^"]+)/gm)]; 62 | } 63 | 64 | // filter out already used properties, even if not saved, like 'comments.lineComment' 65 | 66 | if (found && completionArray) { 67 | completionArray = completionArray.filter(property => !found.find(config => 68 | config[1] === `${ language }.${ property.label }`)); 69 | return completionArray; 70 | } 71 | return completionArray; 72 | } 73 | } 74 | }, 75 | ...['"', '.'] // triggers for intellisense/completion 76 | ); 77 | 78 | extensionContext.subscriptions.push(settingsCompletionProvider); 79 | } 80 | 81 | 82 | /** 83 | * Get an array of all languageIDs 84 | * 85 | * @returns - an array of vscode.CompletionItem's 86 | */ 87 | async function _getCompletionItemsNewLangs() { 88 | 89 | let langIDArray = await vscode.languages.getLanguages(); 90 | 91 | const skipLangs = _getLanguagesToSkip(); 92 | langIDArray = langIDArray.filter(lang => !skipLangs.includes(lang) && !lang.startsWith('csv')); 93 | 94 | return langIDArray.map(lang => new vscode.CompletionItem(lang, vscode.CompletionItemKind.Constant)); 95 | } 96 | 97 | 98 | /** 99 | * Get all the lang config properties for a given language and 100 | * make an array of vscode.CompletionItems's 101 | * 102 | * @param {string} langID 103 | * @param {vscode.Position} position 104 | * @param {vscode.ExtensionContext} context 105 | * @returns - an array of vscode.CompletionItem's 106 | */ 107 | function _getCompletionItemsProperties(langID, position, context) { 108 | 109 | let completionItemArray = []; 110 | const langConfigPath = path.join(context.globalStorageUri.fsPath, 'languageProperties', `${ langID }.json`); 111 | 112 | if (fs.existsSync(langConfigPath)) { 113 | const properties = require(langConfigPath); 114 | 115 | for (const property of Object.entries(properties)) { 116 | // filter out anything but comments or brackets here 117 | if (property[0].replace(/^([^.]*)\..*/m, '$1') === 'comments' || property[0] === "brackets" || property[0] === "autoClosingPairs") 118 | //if (property[0].replace(/^([^.]*)\..*/m, '$1') === 'comments' || property[0] === "brackets") 119 | completionItemArray.push(makeCompletionItem(property, position)); 120 | } 121 | return completionItemArray; 122 | } 123 | } 124 | 125 | /** 126 | * From a string input make a CompletionItemKind.Text 127 | * 128 | * @param {string|Array} key 129 | * @param {vscode.Position} position 130 | * @returns - CompletionItemKind.Text 131 | */ 132 | function makeCompletionItem(key, position) { 133 | 134 | let item; 135 | 136 | // only from _getCompletionItemsNewLangs() and trigger character '"' 137 | if (typeof key === 'string') { 138 | item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Text); 139 | item.range = new vscode.Range(position, position); 140 | } 141 | else if (typeof key[1] === 'string') { 142 | item = new vscode.CompletionItem(key[0], vscode.CompletionItemKind.Value); 143 | item.range = new vscode.Range(position, position); 144 | item.detail = `string : "${escape2(key[1])}"`; 145 | item.insertText = `${key[0]}": "${escape2(key[1])}`; 146 | } 147 | else { // Array.isArray(key[1]) === true, brackets/blockComment 148 | item = new vscode.CompletionItem(key[0], vscode.CompletionItemKind.Value); 149 | let keyStringified = JSON.stringify(key[1]); 150 | item.range = { inserting: new vscode.Range(position, position), replacing: new vscode.Range(position, new vscode.Position(position.line, position.character + 1)) }; 151 | item.detail = `array : ${ keyStringified }`; 152 | item.insertText = `${key[0]}": ${ keyStringified }`; 153 | } 154 | 155 | return item; 156 | } 157 | 158 | /** 159 | * These "languages" will not be indexed for their properties 160 | * because they do not have comments, for example. 161 | * @returns {string[]} 162 | */ 163 | function _getLanguagesToSkip () { 164 | // return ['code-text-binary', 'bibtex', 'log', 'Log', 'search-result', 'plaintext', 'juliamarkdown', 'scminput', 'properties', 'csv', 'tsv', 'excel']; 165 | return ['code-text-binary', 'bibtex', 'log', 'Log', 'search-result', 'plaintext', 'juliamarkdown', 'scminput', 'csv', 'tsv', 'excel']; 166 | } 167 | 168 | /** 169 | * Escape certain values. 170 | * @param {string} value 171 | * @returns {string} 172 | */ 173 | function escape2(value) { 174 | if (typeof(value) !== "string") return value; 175 | return value 176 | .replace(/\\([^"\\|])/g, '\\\\$1') 177 | .replace(/\\\"/g, '\\\\\\\"') 178 | .replace(/\\\\\\\|/g, '\\\\\\\\\\\\|') 179 | .replace(/(? { 30 | 31 | // "languages" to skip, like plaintext, etc. = no configuration properties that we are interested in 32 | let skipLangs = _getLanguagesToSkip(); 33 | 34 | if (!skipLangs?.includes(packageLang.id) && _ext.packageJSON.contributes.languages[index].configuration) { 35 | 36 | langConfigFilePath = path.join( 37 | _ext.extensionPath, 38 | _ext.packageJSON.contributes.languages[index].configuration 39 | ); 40 | 41 | if (!!langConfigFilePath && fs.existsSync(langConfigFilePath)) { 42 | let langID = packageLang.id.replace(/^(.*\.)?(.+)$/m, '$2'); 43 | const thisConfig = JSON.stringify(jsonc.parse(fs.readFileSync(langConfigFilePath).toString())); 44 | 45 | // const destPath = path.join(extConfigDirectory, `${packageLang.id}-language.json`); 46 | const destPath = path.join(extConfigDirectory, `${langID}-language.json`); 47 | if (!fs.existsSync(destPath)) { 48 | newLanguageCount++; 49 | newLanguages.push(`${packageLang.id}`); 50 | } 51 | 52 | fs.writeFileSync(destPath, thisConfig, { flag: 'w' }); 53 | } 54 | // destPath = "c:\\Users\\Mark\\AppData\\Roaming\\Code - Insiders\\User\\globalStorage\\arturodent.custom-language-syntax\\languageConfigs\\erb-language.json" 55 | } 56 | }); 57 | } 58 | } 59 | return {newLanguageCount, newLanguages}; 60 | }; 61 | 62 | /** 63 | * @param {string} langID - 64 | */ 65 | exports.getLanguageConfigFile = async function (langID) { 66 | 67 | let thisConfig = {}; 68 | let langConfigFilePath = null; 69 | 70 | for (let i = 0; i < vscode.extensions.all.length; i++) { 71 | 72 | let _ext = vscode.extensions.all[i]; 73 | 74 | if (_ext.packageJSON.contributes && _ext.packageJSON.contributes.languages) { 75 | 76 | const contributedLanguages = _ext.packageJSON.contributes.languages; // may be an array 77 | 78 | for (let j = 0; j < contributedLanguages.length; j++) { 79 | 80 | let packageLang = contributedLanguages[j]; 81 | 82 | if (packageLang.id === langID) { 83 | 84 | // "languages" to skip, like plaintext, etc. = no configuration properties that we are interested in 85 | let skipLangs = _getLanguagesToSkip(); 86 | 87 | // if (!skipLangs?.includes(packageLang.id) && _ext.packageJSON.contributes.languages[index].configuration) { 88 | if (!skipLangs?.includes(packageLang.id) && _ext.packageJSON.contributes.languages[j].configuration) { 89 | 90 | 91 | langConfigFilePath = path.join( 92 | _ext.extensionPath, 93 | // _ext.packageJSON.contributes.languages[index].configuration 94 | _ext.packageJSON.contributes.languages[j].configuration 95 | 96 | ); 97 | 98 | if (!!langConfigFilePath && fs.existsSync(langConfigFilePath)) { 99 | thisConfig = jsonc.parse(fs.readFileSync(langConfigFilePath).toString()); 100 | return thisConfig; 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | return thisConfig; 108 | }; 109 | 110 | 111 | /** 112 | * SHow the language configuration file for the current editor 113 | * @param {string} langConfigFilePath - vscode.window.activeTextEditor.document.languageId 114 | */ 115 | exports.showLanguageConfigFile = async function (langConfigFilePath) { 116 | 117 | let success = false; 118 | 119 | for (const _ext of vscode.extensions.all) { 120 | 121 | if (_ext.packageJSON.contributes && _ext.packageJSON.contributes.languages) { 122 | 123 | const packageLang = _ext.packageJSON.contributes.languages; // could be an array 124 | 125 | let index = 0; 126 | 127 | for (const lang of packageLang) { 128 | 129 | if (lang.id === langConfigFilePath) { 130 | 131 | let filePath = path.join( 132 | _ext.extensionPath, 133 | _ext.packageJSON.contributes.languages[index].configuration 134 | ); 135 | if (!!langConfigFilePath && fs.existsSync(filePath)) { 136 | await vscode.window.showTextDocument(vscode.Uri.file(filePath)); 137 | await vscode.commands.executeCommand('editor.action.formatDocument'); 138 | success = true; 139 | } 140 | break; 141 | } 142 | }; 143 | } 144 | 145 | if (success) break; 146 | // TODO else { show notification message can't find a language-configuration.json file } 147 | } 148 | } 149 | 150 | 151 | /** 152 | * Transform all language-configuration.json files to 'comments.lineComment' form and 153 | * remove properties that can not be currently set. 154 | * 155 | * @param {vscode.ExtensionContext} context 156 | * @param {string} extConfigDirectory - 157 | * @param {string} extLangPropDirectory - 158 | */ 159 | exports.reduceFiles = async function (context, extConfigDirectory, extLangPropDirectory) { 160 | 161 | if (!fs.existsSync(extLangPropDirectory)) fs.mkdirSync(extLangPropDirectory,{ recursive: true }); 162 | 163 | // const configSet = new Set(['comments', 'brackets', 'indentationRules', 'onEnterRules', 'wordPattern']); 164 | const configSet = new Set(['comments', 'brackets', 'indentationRules', 'onEnterRules', 'wordPattern', 'autoClosingPairs']); 165 | const configDir = fs.readdirSync(extConfigDirectory, 'utf8'); 166 | 167 | for (const lang of configDir) { 168 | 169 | /** @type {Object}*/ 170 | let fileObject = {}; 171 | 172 | let langJSON = require(path.join(extConfigDirectory, lang)); 173 | 174 | configSet.forEach(config => { 175 | 176 | if (langJSON[config]) { 177 | switch (config) { 178 | case 'comments': 179 | if (langJSON.comments.lineComment) fileObject['comments.lineComment'] = langJSON.comments.lineComment; 180 | if (langJSON.comments.blockComment) fileObject['comments.blockComment'] = langJSON.comments.blockComment; 181 | break; 182 | case 'brackets': 183 | fileObject['brackets'] = langJSON.brackets; 184 | break; 185 | case 'indentationRules': 186 | if (langJSON.indentationRules.increaseIndentPattern) fileObject['indentationRules.increaseIndentPattern'] = langJSON.indentationRules.increaseIndentPattern; 187 | if (langJSON.indentationRules.decreaseIndentPattern) fileObject['indentationRules.decreaseIndentPattern'] = langJSON.indentationRules.decreaseIndentPattern; 188 | break; 189 | case 'onEnterRules': 190 | if (langJSON.onEnterRules.action) fileObject['onEnterRules.action'] = langJSON.onEnterRules.action; 191 | if (langJSON.onEnterRules.afterText) fileObject['onEnterRules.afterText'] = langJSON.onEnterRules.afterText; 192 | if (langJSON.onEnterRules.beforeText) fileObject['onEnterRules.beforeText'] = langJSON.onEnterRules.beforeText; 193 | break; 194 | case 'wordPattern': 195 | fileObject['wordPattern'] = langJSON.wordPattern; 196 | break; 197 | 198 | // TODO add autoClosingPairs from proposed api 199 | case 'autoClosingPairs': 200 | // { 201 | // "open": "{", 202 | // "close": "}" 203 | // }, 204 | // { 205 | // "open": "<", 206 | // "close": ">", 207 | // "notIn": [ 208 | // "string" 209 | // ] 210 | // } 211 | fileObject['autoClosingPairs'] = langJSON.autoClosingPairs; 212 | break; 213 | 214 | default: 215 | break; 216 | } 217 | } 218 | }) 219 | const langID = lang.replace(/(^\S*)-\S*$/m, '$1'); // bat-language.json or objective-c-language.json 220 | const configTargetPath = path.join(extLangPropDirectory, `${langID}.json`); 221 | 222 | fs.writeFileSync(configTargetPath, JSON.stringify(fileObject)); 223 | } 224 | } 225 | 226 | /** 227 | * These "languages" will not be indexed for their properties 228 | * because they do not have comments or other applicable properties. 229 | * @returns {string[]} 230 | */ 231 | function _getLanguagesToSkip () { 232 | // return ['log', 'Log', 'search-result', 'plaintext', 'scminput', 'properties', 'csv', 'tsv', 'excel']; 233 | return ['log', 'Log', 'search-result', 'plaintext', 'scminput', 'csv', 'tsv', 'excel']; 234 | } 235 | 236 | // { 237 | // "comments.lineComment": "//", 238 | // "comments.blockComment": [ 239 | // "/*", 240 | // "*/", 241 | // ], 242 | // brackets: [ 243 | // [ 244 | // "{", 245 | // "}", 246 | // ], 247 | // [ 248 | // "[", 249 | // "]", 250 | // ], 251 | // [ 252 | // "(", 253 | // ")", 254 | // ], 255 | // ], 256 | // } 257 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Custom Language Properties 2 | 3 | This extension allows you to set your own language properties for the languages you use. These properties include: 4 | 5 | ```plaintext 6 | comments 7 | brackets 8 | autoClosingPairs // support removed in v0.6.1 9 | ``` 10 | 11 | v0.6.1 - REMOVES support for `autoClosingPairs` because vscode's implementation is buggy. If you really want `autoClosingPairs` support, downgrade this extension to v0.6.0 - and don't use any other options like `comments.lineComment`, etc. 12 | 13 | Not available yet: indentationRules, onEnterRules, and wordPattern. 14 | 15 | Note that any changes you make in your settings in the `"custom-language-properties": {}` do **NOT** edit the underlying language configuration file provided by a language extension. The working language configuration properties are modified by updating a vscode api without affecting any language extension files. 16 | 17 | -------------- 18 | 19 | ## Features 20 | 21 | For example, if you prefer your line comments to look like   `// * `   you can set it to that in specific languages. Here is an example setting in user `settings.json`: 22 | 23 | 28 | 29 | 30 | ```jsonc 31 | "custom-language-properties": { 32 | "javascript.comments.lineComment": "// *", 33 | "python.comments.lineComment": "# *", 34 | "python.brackets": [["{","}"],["[","]"],["(",")"]] 35 | } 36 | ``` 37 | 38 | These are **NOT** additional comment styles, the default comment styles will be replaced by those in your setting. I suggest you do not go crazy with comment style for example, as other editors may not recognize them as comments. So something like   `// **`   is fine (the leading   `//` will still be recognized as a comment) but   `**`   by itself will not be recognized by another editor or person without this extension and that setting. 39 | 40 | The `brackets` setting will replace the entire default language `brackets` configuration. So you probably don't want to do that but instead add another `brackets` option to the end of the default setting. Above is the python default `brackets` setting, to add to it use the same syntax plus your addition, like so: 41 | 42 | ```jsonc 43 | "python.brackets": [["{","}"],["[","]"],["(",")"],["<",">"]] 44 | ``` 45 | 46 | ---------- 47 | 48 | Demo removing backtick completion in a markdown file: 49 | 50 |          demo of removing backtick auto-completion in markdown using this extension 51 | 52 | -------------- 53 | 54 | ## Requirements 55 | 56 | Only these language configuration properties can be modified by this extension at this time:   `comments` and `brackets`. 57 | 58 | Because `indentationRules`, `onEnterRules`, and `wordPattern` use regexp values, I am continuing to work on adding those. 59 | 60 | The built-in `vscode.languages.setLanguageConfiguration()` can only use the above settings. Some other language configuration properties can be set in other ways: 61 | 62 | * Autoclosing behavior with the `Editor: Auto Closing Quotes` and `Editor: Auto Closing Brackets` settings. 63 | * Autosurrounding behavior with the `Editor: Auto Surround` setting. 64 | * Autoclosing comments with the `Editor: Auto Closing Comments` setting.. 65 | 66 | The *default* values are shown where available in the completion suggestion pop-up. Intellisense will complete the default values - you can add or modify those values. You do not need to include the default values, they will automatically be added for you. 67 | 68 | * Tested with settings in the user `settings.json` only as of v0.5.0. 69 | 70 | -------------- 71 | 72 | ## Extension Settings 73 | 74 | This extension contributes one setting: 75 | 76 | * `"custom-language-properties"` - see the example above. 77 | 78 | Note that not all properties are available in each language. For example, `html` doesn't have `lineComment` in its default configuration. So intellisense will not show a `lineComment` option for `html`. 79 | 80 | You **must** use vscode's language identifiers within the setting, like `csharp` or `javascriptreact`. 81 | 82 | * To remove a custom setting provided by this extension, simply delete it or comment-it-out from the `custom-language-properties` settings, save the modified settings file and the default setting will be activated again. 83 | 84 | Where appropriate, intellisense will suggest available language IDs and language properties (like `comments.lineComment`) relevant to each language. 85 | 86 | If you have added a language configuration extension **after the first time this extension is activated**, you will need to re-run the initialization step that finds all language configuration extensions in your workspace and builds the necessary files in the extension's storage to enable intellisense in the settings. 87 | 88 | * Run the command ***Custom Language Properties: Check for new language extensions*** (`custom-language-syntax.rebuildConfigFiles`) to find each installed language configuration extension and build the necessary files. The required files will be automatically saved in the correct location. 89 | 90 | You will get a notifcation when the above command has finished and whether it has found any new langages. All installed languages will be re-examined so that if there were language configuration changes after installing the languages, these changes will be used. If the message indicates that your newly-installed language was found, you can begin using that language in this extension's settings - with intellisense. 91 | 92 | The location of these internal files built and used by this extension are: 93 | 94 | ```plaintext 95 | c:\\Users\\Mark\\AppData\\Roaming\\Code\\User\\globalStorage\\arturodent.custom-language-syntax\\languageConfigs\\erb-language.json 96 | 97 | c:\\Users\\Mark\\AppData\\Roaming\\Code\\User\\globalStorage\\arturodent.custom-language-syntax\\languageProperties\\erb-language.json 98 | 99 | - replace Code with Code-Insiders if necessary 100 | - for nix, search for the "arturodent.custom-language-syntax\\languageConfigs" folder 101 | ``` 102 | 103 | These folders or files can be deleted at any time and the command `Custom Language Properties: Check for new language extensions` be re-run if you think necessary. 104 | 105 | If at any time you just want to see the language configuration file for the current editor's language, you can 106 | 107 | * Run the command ***Custom Language Properties: Show language configuration for current editor*** (`custom-language-syntax.showConfigFile`) to see the current editor's language configuration file. You can modify that file if you wish - that is editing the actual language configuration file provided by the language extension. These changes will affect that language's configurations going forward. But if that language extension is ever updated your changes would be over-written. 108 | 109 | Properties, like `comments.lineComment` already used in the setting for a language are filtered out of the subsequent completion suggestions. The command `custom-language-syntax.showConfigFile` will not get settings that haven't yet been saved. 110 | 111 | * This extension **must** have its own copy of the language configuration file for each languageID that you wish to use in the setting. If you are not getting intellisense in the `"custom-language-properties"` setting for a language's configuration propoerties, like `comments.lineComment` or `brackets` with their default values, run the `custom-language-syntax.rebuildConfigFiles` mentioned above. 112 | 113 | > Note: There is a general setting `Editor > Comments: Insert Space` which, if enabled, will automatically add a space after your comment characters. The default option for this setting is `disabled`. Just be aware that if you have that setting enabled a space will be added after whatever comment character(s) you set in this extension's settings. 114 | 115 | -------------- 116 | 117 | ## TODO 118 | 119 | [ ] - Investigate how to get `indentationRules`, `onEnterRules`, and `wordPattern` regex values working. 120 | [ ] - Fix intellisense for partial completions in settings. 121 | 122 | ## Release Notes 123 | 124 | 0.0.2 - Added loading of each new config upon focusing a different file languageId. 125 | 0.0.3 - Name and setting change to `"custom-language-properties"`. 126 | 0.0.4 - Added completionProvider for all languageIds and each language's available properties. 127 | 0.1.0 - Fixed the completionProvider to not delete omitted properties. 128 |    Scraped and reduced all language configuration files in the default vscode setup. 129 | 0.1.5 - Added filters for comments/brackets and for already used properties in the setting. 130 | 0.2.0 - `getLanguageConfigFiles()` changed to handle arrays of contributed languages. 131 | 0.3.0 - Added get input from user for unknown languages. 132 | 0.4.0 - Added lisp ([lisp language extension](https://marketplace.visualstudio.com/items?itemName=mattn.Lisp)) to supported languages. 133 | 0.5.0 - Simplified configuration file creation. 134 |    Using `jsonc-parser` for configuration files. 135 |    New command: `custom-language-syntax.rebuildConfigFiles`. 136 |    0.5.2 - Better intellisense. Refactor getting settings and setting configurations. 137 | 138 | 0.6.0 - Added support for `autoClosingPairs`. 139 | 140 | 0.6.1 - REMOVED support for `autoClosingPairs`. 141 | 142 | 0.6.2 - Added support for `properties` language mode. 143 | 144 | 0.6.4 - Added support for 'html.erb' type of language id's. 145 | 0.6.5 - Added a notification for when new languages have been found. 146 | 0.6.6 - Removed git dependency? How did it get there at all? 147 | 148 | -------------- 149 | --------------------------------------------------------------------------------