├── test ├── data │ ├── folding │ │ ├── .gitkeep │ │ ├── Pet.json │ │ ├── List.json │ │ ├── Dog.json │ │ ├── Cat.json │ │ ├── Animal.json │ │ ├── Snake.json │ │ └── Horse.json │ ├── outline │ │ ├── .gitkeep │ │ ├── Pet.json │ │ ├── List.json │ │ ├── Dog.json │ │ ├── Snake.json │ │ ├── Horse.json │ │ └── Cat.json │ ├── selectors │ │ ├── .gitkeep │ │ ├── map.json │ │ └── selector.json │ ├── tokenizer │ │ └── .gitkeep │ ├── definition │ │ ├── .gitkeep │ │ ├── Pet.json │ │ ├── Animal.json │ │ ├── Cat.json │ │ ├── Dog.json │ │ ├── Horse.json │ │ └── Snake.json │ ├── document-symbol │ │ ├── .gitkeep │ │ ├── List.json │ │ ├── Pet.json │ │ └── Dog.json │ └── workspace-symbol │ │ ├── .gitkeep │ │ └── index.json ├── extensions │ ├── vscode-typescript │ │ ├── syntaxes │ │ │ └── .gitkeep │ │ ├── textmate-configuration.json │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── src │ │ │ └── extension.ts │ │ ├── README.md │ │ └── package.json │ ├── vscode-mediawiki │ │ ├── textmate-configuration.json │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── src │ │ │ └── extension.ts │ │ ├── README.md │ │ ├── package.json │ │ └── language-configuration.json │ └── vscode-vue │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── src │ │ └── extension.ts │ │ ├── syntaxes │ │ ├── vue-directives.json │ │ ├── vue-interpolations.json │ │ └── vue-sfc-style-variable-injection.json │ │ ├── README.md │ │ ├── language-configuration.json │ │ └── package.json ├── types │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── api-tests.ts │ └── service-tests.ts ├── util │ ├── runtime.ts │ ├── files.ts │ ├── common.ts │ ├── jsonify.ts │ ├── bench.ts │ ├── factory.ts │ └── assert.ts ├── context │ ├── secret.ts │ ├── memento.ts │ ├── index.ts │ ├── variables.ts │ └── mock.ts ├── samples │ ├── Dog.m │ ├── Snake.m │ ├── Cat.m │ ├── Horse.m │ ├── Pet.ts │ ├── Animal.m │ ├── Hello.vue │ └── List.wiki ├── tsconfig.json ├── harness-electron.ts ├── harness-web.ts ├── suite │ ├── providers │ │ ├── workspace-symbol.test.ts │ │ ├── document-symbol.test.ts │ │ ├── folding.test.ts │ │ └── definition.test.ts │ ├── services │ │ ├── outline.test.ts │ │ ├── tokenizer.test.ts │ │ ├── document.test.ts │ │ └── selectors.test.ts │ └── api │ │ ├── language-contribution.test.ts │ │ └── token-information.test.ts ├── runner-web.ts ├── runner-electron.ts └── patches │ └── vscode-matlab │ └── textmate-configuration.json ├── .gitconfig ├── scripts ├── silent-formatter.js ├── bundle.js ├── reset.js ├── i18n.js ├── stage.js └── pegjs.js ├── .markdownlintignore ├── assets ├── logo.png ├── demo-outline.png └── compatibility-badge.svg ├── .gitattributes ├── .gitmodules ├── typings ├── imports.d.ts ├── context.d.ts └── token-information.api.d.ts ├── .eslintignore ├── .prettierrc.json ├── SECURITY.md ├── .github ├── stale.yml ├── ISSUE_TEMPLATE │ ├── --new-release.md │ ├── --question.md │ ├── --feature-request.md │ └── --bug-report.md ├── workflows │ ├── issue.yml │ └── ci.yml └── pull_request_template.md ├── tsconfig.json ├── src ├── scopes │ ├── header.ts │ ├── pegconfig.json │ ├── parser.pegjs │ └── index.ts ├── services │ ├── generators.ts │ ├── outline.ts │ ├── tokenizer.ts │ └── resolver.ts ├── util │ ├── crypto.ts │ ├── fast-selector.ts │ ├── lazy.ts │ ├── dispose.ts │ ├── loader.ts │ ├── oniguruma.ts │ ├── service.ts │ └── selectors.ts ├── i18n.json ├── config │ ├── selectors.ts │ └── index.ts ├── definition.ts ├── document-symbol.ts ├── workspace-symbol.ts └── api.ts ├── .npmignore ├── .gitignore ├── .editorconfig ├── .vscode └── settings.json ├── webpack.config.js ├── .markdownlint.json ├── webpack.config.web.test.js ├── LICENSE.md └── CONTRIBUTING.md /test/data/folding/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/outline/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/selectors/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/tokenizer/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/definition/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/folding/Pet.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/outline/Pet.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | pager = cat 3 | -------------------------------------------------------------------------------- /test/data/definition/Pet.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/document-symbol/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/folding/List.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/outline/List.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/workspace-symbol/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/definition/Animal.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/document-symbol/List.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/data/document-symbol/Pet.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/syntaxes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/silent-formatter.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ''; 2 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/textmate-configuration.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/textmate-configuration.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | .github/ISSUE_TEMPLATE/*.md 2 | .github/pull_request_template.md 3 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vsce-toolroom/vscode-textmate-languageservice/HEAD/assets/logo.png -------------------------------------------------------------------------------- /test/extensions/vscode-vue/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | node_modules/ 3 | out/ 4 | samples/ 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | node_modules/ 3 | out/ 4 | samples/ 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | node_modules/ 3 | out/ 4 | samples/ 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /assets/demo-outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vsce-toolroom/vscode-textmate-languageservice/HEAD/assets/demo-outline.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.m linguist-language=matlab 2 | package-lock.json -diff 3 | test/extensions/vscode-mediawiki/syntaxes/mediawiki.tmLanguage.json -diff 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/extensions/vscode-matlab"] 2 | path = test/extensions/vscode-matlab 3 | url = https://github.com/Gimly/vscode-matlab 4 | ignore = all 5 | -------------------------------------------------------------------------------- /typings/imports.d.ts: -------------------------------------------------------------------------------- 1 | // wiring from webpack `encoded-uint8array-loader` to inline WASM buffer view 2 | declare module '*.wasm' { 3 | const bufview: Uint8Array; 4 | export = bufview; 5 | } 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | scripts 5 | src/parser/parser.ts 6 | src/scopes/header.ts 7 | src/typing/*.d.ts 8 | test/extensions 9 | test/samples 10 | webpack.*.js 11 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["es2019"], 5 | "outDir": "out", 6 | "rootDir": ".", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "baseUrl": "." 10 | }, 11 | "exclude": ["./samples/*"] 12 | } 13 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["es2019"], 5 | "outDir": "out", 6 | "rootDir": ".", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "baseUrl": "." 10 | }, 11 | "exclude": ["./samples/*"] 12 | } 13 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["es2019"], 5 | "outDir": "out", 6 | "rootDir": ".", 7 | "moduleResolution": "node", 8 | "module": "commonjs", 9 | "baseUrl": "." 10 | }, 11 | "exclude": ["samples"] 12 | } 13 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "tabWidth": 4, 5 | "parser": "typescript", 6 | "trailingComma": "none", 7 | "arrowParens": "avoid", 8 | "bracketSpacing": true, 9 | "singleQuote": true, 10 | "eslintIntegration": true, 11 | "jsxSingleQuote": true, 12 | "endOfLine": "crlf" 13 | } 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | > Previous versions of the package (`^0.2.0`) are vulnerable to major performance defects that behave as DOS in the extension host. Upgrade to `^1.1.0` **ASAP** to prevent this. 2 | 3 | If you have discovered a security vulnerability, please email me ASAP. 4 | 5 | My email is as recorded in the `author.email` field of `package.json`. 6 | -------------------------------------------------------------------------------- /test/data/folding/Dog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "start": 2, 4 | "end": 3 5 | }, 6 | { 7 | "start": 5, 8 | "end": 6 9 | }, 10 | { 11 | "start": 8, 12 | "end": 10 13 | }, 14 | { 15 | "start": 12, 16 | "end": 14 17 | }, 18 | { 19 | "start": 1, 20 | "end": 15 21 | }, 22 | { 23 | "start": 0, 24 | "end": 16 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 60 2 | daysUntilClose: 7 3 | exemptLabels: 4 | - modifier-pinned 5 | - scope-security 6 | staleLabel: resolution-stale 7 | markComment: > 8 | This issue has been automatically marked as stale because it has not had 9 | recent activity. It will be closed if no further activity occurs. Thank you 10 | for your contributions. 11 | closeComment: false 12 | -------------------------------------------------------------------------------- /test/types/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "plugin:expect-type/recommended" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "project": "./test/types/tsconfig.json" 13 | }, 14 | "plugins": [ 15 | "eslint-plugin-expect-type" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /test/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["es2022"], 5 | "outDir": "out", 6 | "rootDir": ".", 7 | "moduleResolution": "node", 8 | "module": "esnext" 9 | }, 10 | "include": [ 11 | "../../test/types/*.ts", 12 | "../../typings/token-information.api.d.ts", 13 | "../../typings/imports.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "commonjs", 5 | "outDir": "out", 6 | "declarationDir": "dist/types", 7 | "declaration": true, 8 | "types": ["node", "webpack-env"], 9 | "resolveJsonModule": true, 10 | "lib": ["es2019", "dom"], 11 | "target": "es2019" 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import TextmateLanguageService from 'vscode-textmate-languageservice'; 3 | 4 | export async function activate(context: vscode.ExtensionContext) { 5 | const selector: vscode.DocumentSelector = 'vue'; 6 | const textmateService = new TextmateLanguageService(selector, context); 7 | } 8 | 9 | export function deactivate() {} 10 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import TextmateLanguageService from 'vscode-textmate-languageservice'; 3 | 4 | export async function activate(context: vscode.ExtensionContext) { 5 | const selector: vscode.DocumentSelector = 'typescript'; 6 | const textmateService = new TextmateLanguageService(selector); 7 | } 8 | 9 | export function deactivate() {} 10 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import TextmateLanguageService from 'vscode-textmate-languageservice'; 3 | 4 | export async function activate(context: vscode.ExtensionContext) { 5 | const selector: vscode.DocumentSelector = 'mediawiki'; 6 | const textmateService = new TextmateLanguageService(selector, context); 7 | } 8 | 9 | export function deactivate() {} 10 | -------------------------------------------------------------------------------- /src/scopes/header.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) GitHub Inc. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as matchers from './matchers'; 8 | -------------------------------------------------------------------------------- /test/data/folding/Cat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "start": 2, 4 | "end": 3 5 | }, 6 | { 7 | "start": 5, 8 | "end": 6 9 | }, 10 | { 11 | "start": 8, 12 | "end": 10 13 | }, 14 | { 15 | "start": 12, 16 | "end": 14 17 | }, 18 | { 19 | "start": 1, 20 | "end": 15 21 | }, 22 | { 23 | "start": 0, 24 | "end": 16 25 | }, 26 | { 27 | "start": 20, 28 | "end": 22 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--new-release.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🚀 New Release" 3 | about: "Publish changes 📦" 4 | labels: [type-release] 5 | 6 | --- 7 | 8 | ## 📦 `#.#.#` 9 | 10 | - [ ] Cross-channel testing for `#.#.#` (VS Code, Code Insiders, GitHub Codespaces, VSCodium). 11 | - [ ] Update asset references. 12 | - [ ] Create tag release for GitHub. 13 | - [ ] Publish `#.#.#` to NPM. 14 | 15 | ## Changelog 16 | 17 | - Change 1 18 | - ... 19 | -------------------------------------------------------------------------------- /test/util/runtime.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | // The API for runtime detection is frankly not sane. 6 | // This is the best way to detect if we are in a web runtime. 7 | // microsoft/vscode#104436; microsoft/vscode#134568 8 | const isWebUI = vscode.env.uiKind === vscode.UIKind.Web; 9 | const isRemote = typeof vscode.env.remoteName === 'string'; 10 | export const isWebRuntime = isWebUI && !isRemote; 11 | -------------------------------------------------------------------------------- /src/services/generators.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import TextmateLanguageService from '../main'; 4 | import { ServiceBase } from '../util/service'; 5 | 6 | export class GeneratorService extends ServiceBase { 7 | constructor( 8 | ) { 9 | super(); 10 | } 11 | 12 | public parse(languageId: string): Promise { 13 | return Promise.resolve(new TextmateLanguageService(languageId)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/scopes/pegconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "returnTypes": { 3 | "Start": "matchers.ParsedMatcher", 4 | "Atom": "matchers.AtomMatcher", 5 | "Scope": "matchers.ScopeMatcher", 6 | "Path": "matchers.PathMatcher", 7 | "Group": "matchers.GroupMatcher", 8 | "Expression": "matchers.ExpressionMatcherType", 9 | "Composite": "matchers.CompositeMatcherType", 10 | "Selector": "matchers.ParsedMatcher", 11 | "_": "Array<' ' | '\t'>" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/syntaxes/vue-directives.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "fileTypes": [], 4 | "injectionSelector": "L:meta.tag -meta.attribute -meta.ng-binding -entity.name.tag.pug -attribute_value -source.tsx -source.js.jsx, L:meta.element -meta.attribute", 5 | "patterns": [ 6 | { 7 | "include": "source.vue#vue-directives" 8 | } 9 | ], 10 | "scopeName": "vue.directives" 11 | } 12 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/syntaxes/vue-interpolations.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "fileTypes": [], 4 | "injectionSelector": "L:text.pug -comment -string.comment, L:text.html.derivative -comment.block, L:text.html.markdown -comment.block", 5 | "patterns": [ 6 | { 7 | "include": "source.vue#vue-interpolations" 8 | } 9 | ], 10 | "scopeName": "vue.interpolations" 11 | } 12 | -------------------------------------------------------------------------------- /scripts/bundle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const shelljs = require('shelljs'); 4 | 5 | shelljs.exec('webpack-cli --config webpack.config.js --stats-error-details'); 6 | 7 | shelljs.exec('tsc --project ./test/tsconfig.json'); 8 | shelljs.mkdir('./dist/test'); 9 | shelljs.cp('-r', './out/test/*', './dist/test'); 10 | 11 | shelljs.exec('webpack-cli --config webpack.config.web.test.js --stats-error-details'); 12 | 13 | shelljs.rm('-rf', 'out', './dist/types/test', 'dist/test/data/selectors'); 14 | -------------------------------------------------------------------------------- /test/data/folding/Animal.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "start": 1, 4 | "end": 6 5 | }, 6 | { 7 | "start": 9, 8 | "end": 14 9 | }, 10 | { 11 | "start": 16, 12 | "end": 17 13 | }, 14 | { 15 | "start": 19, 16 | "end": 20 17 | }, 18 | { 19 | "start": 22, 20 | "end": 23 21 | }, 22 | { 23 | "start": 25, 24 | "end": 26 25 | }, 26 | { 27 | "start": 8, 28 | "end": 27 29 | }, 30 | { 31 | "start": 0, 32 | "end": 28 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /test/data/folding/Snake.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "start": 2, 4 | "end": 3 5 | }, 6 | { 7 | "start": 5, 8 | "end": 6 9 | }, 10 | { 11 | "start": 9, 12 | "end": 10 13 | }, 14 | { 15 | "start": 11, 16 | "end": 12 17 | }, 18 | { 19 | "start": 8, 20 | "end": 14 21 | }, 22 | { 23 | "start": 16, 24 | "end": 18 25 | }, 26 | { 27 | "start": 1, 28 | "end": 19 29 | }, 30 | { 31 | "start": 0, 32 | "end": 20 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /test/context/secret.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | export class StubSecretStorage { 6 | public onDidChange = new vscode.EventEmitter().event; 7 | public get(_: string): Thenable { 8 | return Promise.resolve('●●●●●●●●●●●●●●●●'); 9 | } 10 | public store(_1: string, _2: string): Thenable { 11 | return Promise.resolve(); 12 | } 13 | public delete(_1: string): Thenable { 14 | return Promise.resolve(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "❓ Question" 3 | about: "There's something unclear 😕" 4 | labels: [type-question] 5 | 6 | --- 7 | 8 | ## ❓ Question 9 | 10 | ### Describe your question 11 | 12 | 13 | 14 | ### Search tags, topics 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/data/folding/Horse.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "start": 2, 4 | "end": 3 5 | }, 6 | { 7 | "start": 5, 8 | "end": 6 9 | }, 10 | { 11 | "start": 9, 12 | "end": 10 13 | }, 14 | { 15 | "start": 11, 16 | "end": 12 17 | }, 18 | { 19 | "start": 13, 20 | "end": 14 21 | }, 22 | { 23 | "start": 8, 24 | "end": 16 25 | }, 26 | { 27 | "start": 18, 28 | "end": 20 29 | }, 30 | { 31 | "start": 1, 32 | "end": 21 33 | }, 34 | { 35 | "start": 0, 36 | "end": 22 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .vscode-test/ 3 | .vscode-test-web/ 4 | .github/ 5 | assets/ 6 | builds/ 7 | out/ 8 | scripts/ 9 | src/ 10 | test/ 11 | !dist/** 12 | *.tgz 13 | *.vsix 14 | **/*.pegjs 15 | .editorconfig 16 | .eslintignore 17 | .gitattributes 18 | .gitconfig 19 | .gitignore 20 | .gitmodules 21 | .markdownlintignore 22 | .markdownlint.json 23 | .prettierrc.json 24 | .travis.yml 25 | .eslintignore 26 | .eslintrc.json 27 | CODE_OF_CONDUCT.md 28 | tsconfig.json 29 | tsconfig.*.json 30 | vscode-web-insider-*-tmp 31 | webpack.config.js 32 | webpack.config.*.*.js 33 | -------------------------------------------------------------------------------- /scripts/reset.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const shelljs = require('shelljs'); 4 | const path = require('path'); 5 | 6 | const root = path.resolve(__dirname, '..'); 7 | const extensionPath = path.resolve(root, process.argv[2]); 8 | const extensionTestDataFolders = ['data', 'samples']; 9 | 10 | shelljs.cd(extensionPath); 11 | shelljs.exec('npm install vscode-textmate-languageservice@latest'); 12 | shelljs.cd(root); 13 | 14 | shelljs.rm('-rf', ...extensionTestDataFolders.map(f => `${extensionPath}/${f}`)); 15 | shelljs.rm('-rf', "*.tgz", `${extensionPath}/*.tgz`); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | out/ 4 | # Mocha hotfix for extension submodules 5 | test/extensions/*/_mocha 6 | test/extensions/*/_mocha.* 7 | test/extensions/*/mocha 8 | test/extensions/*/mocha.* 9 | test/extensions/*/vscode-test-web 10 | test/extensions/*/vscode-test-web.* 11 | # Performance hotfix for matlab submodule 12 | test/extensions/vscode-matlab/data/ 13 | test/extensions/vscode-matlab/samples/ 14 | test/extensions/vscode-matlab/*.tgz 15 | # We target VS Code 16 | .vscode-test/ 17 | .vscode-test-web/ 18 | vscode-web-insider-*-tmp 19 | *.tgz 20 | *.vsix 21 | -------------------------------------------------------------------------------- /test/samples/Dog.m: -------------------------------------------------------------------------------- 1 | classdef Dog < Animal 2 | methods 3 | function obj = Dog(name, years) 4 | obj@Animal(name, years * 15, "Canis", true); 5 | end 6 | function obj = noise(obj) 7 | disp("Woof"); 8 | end 9 | function obj = move(obj, meters) 10 | disp("Pacing.."); 11 | move@Animal(obj, meters / 3); 12 | end 13 | function obj = sleep(obj, multiplier) 14 | disp("Napping.."); 15 | sleep@Animal(obj, multiplier * 12); 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /typings/context.d.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | 3 | // wiring for global test variables in runtimes 4 | declare global { 5 | interface Window { 6 | languageId: string; 7 | extensionContext: vscode.ExtensionContext; 8 | } 9 | namespace NodeJS { 10 | interface Global { 11 | languageId: string; 12 | extensionContext: vscode.ExtensionContext; 13 | } 14 | } 15 | var languageId: string; 16 | var extensionContext: vscode.ExtensionContext; 17 | } 18 | 19 | declare var languageId: string; 20 | declare var extensionContext: vscode.ExtensionContext; 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Tab indentation 7 | [*] 8 | charset = utf-8 9 | end_of_line = crlf 10 | indent_style = tab 11 | insert_final_newline = true 12 | tab_width = 4 13 | 14 | # Space indentiation for MATLAB 15 | [*.m] 16 | indent_style = space 17 | indent_size = 4 18 | 19 | # The indent size used in the `package.json` file cannot be changed 20 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 21 | [{*.yml,*.yaml,*.json}] 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /test/context/memento.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | 5 | export class MockMemento implements vscode.Memento { 6 | public keys(): readonly string[] { 7 | return []; 8 | } 9 | public get(_1: string): T; 10 | public get(_1: string, _2?: T): T { 11 | return void 0 as T; 12 | } 13 | public update(_1: string, _2: any): Thenable { 14 | return Promise.resolve(); 15 | } 16 | } 17 | 18 | export class MockGlobalMemento extends MockMemento implements vscode.Memento { 19 | public setKeysForSync(_: readonly string[]): void { 20 | return void 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/README.md: -------------------------------------------------------------------------------- 1 | # `vscode-vue` 2 | 3 | This is a sample extension demonstrating how to wire a grammar and language configuration to the [`vscode-textmate-languageservice`](../../../README.md) library using `contributes`: 4 | 5 | ```json 6 | { 7 | "contributes": { 8 | "languages": [{ 9 | "id": "lua", 10 | "aliases": ["Lua"], 11 | "extensions": [".lua", ".moon", ".luau"], 12 | "configuration": "./language-configuration.json" 13 | }], 14 | "grammars": [{ 15 | "language": "lua", 16 | "scopeName": "source.lua", 17 | "path": "./syntaxes/Lua.tmLanguage.json" 18 | }] 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/workflows/issue.yml: -------------------------------------------------------------------------------- 1 | name: Issue automation 2 | 3 | on: 4 | issues: 5 | types: [opened, closed] 6 | pull_request: 7 | types: [opened, closed] 8 | 9 | jobs: 10 | automate-issues-labels: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Label open issue 14 | if: "github.event.action == 'opened'" 15 | uses: andymckay/labeler@master 16 | with: 17 | add-labels: "status-tracking" 18 | - name: Label closed issue 19 | if: "github.event.action == 'closed'" 20 | uses: andymckay/labeler@master 21 | with: 22 | remove-labels: "status-tracking" 23 | -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/README.md: -------------------------------------------------------------------------------- 1 | # `vscode-typescript-textmate` 2 | 3 | This is a sample extension demonstrating how the [`vscode-textmate-languageservice`](../../../README.md) library supports built-in languages. 4 | 5 | ```json 6 | { 7 | "textmate-languageservice-contributes": { 8 | "languages": [{ 9 | "id": "lua", 10 | "aliases": ["Lua"], 11 | "extensions": [".lua", ".moon", ".luau"], 12 | "configuration": "./language-configuration.json" 13 | }], 14 | "grammars": [{ 15 | "language": "lua", 16 | "scopeName": "source.lua", 17 | "path": "./syntaxes/Lua.tmLanguage.json" 18 | }] 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "commonjs", 5 | "outDir": "../out", 6 | "types": ["node", "webpack-env", "mocha"], 7 | "rootDir": "..", 8 | "resolveJsonModule": true, 9 | "baseUrl": "..", 10 | "lib": ["es2019", "dom"], 11 | "target": "es2019" 12 | }, 13 | "include": [ 14 | "../typings/token-information.api.d.ts", 15 | "../typings/imports.d.ts", 16 | "../typings/context.d.ts", 17 | "./*", 18 | "./suite/*/*.test", 19 | "./util/*" 20 | ], 21 | "exclude": [ 22 | "../src/main", 23 | "./runner-web", 24 | "./data/*/*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/README.md: -------------------------------------------------------------------------------- 1 | # `vscode-mediawiki` 2 | 3 | This is a sample extension demonstrating how to wire a grammar and language configuration to the [`vscode-textmate-languageservice`](../../../README.md) library using `textmate-languageservice-contributes`: 4 | 5 | ```json 6 | { 7 | "textmate-languageservice-contributes": { 8 | "languages": [{ 9 | "id": "lua", 10 | "aliases": ["Lua"], 11 | "extensions": [".lua", ".moon", ".luau"], 12 | "configuration": "./language-configuration.json" 13 | }], 14 | "grammars": [{ 15 | "language": "lua", 16 | "scopeName": "source.lua", 17 | "path": "./syntaxes/Lua.tmLanguage.json" 18 | }] 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /test/types/api-tests.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import TextmateLanguageService from '../..'; 3 | 4 | const { getTokenInformationAtPosition } = TextmateLanguageService.api; 5 | 6 | const editor = vscode.window.activeTextEditor; 7 | const document = editor.document; 8 | const position = editor.selection.active; 9 | 10 | // $ExpectType TokenInformation 11 | const token = await getTokenInformationAtPosition(document, position); 12 | 13 | // $ExpectType StandardTokenType 14 | const tokenType = token.type; 15 | 16 | // $ExpectType string 17 | const tokenTypeKey = vscode.StandardTokenType[tokenType]; 18 | 19 | void vscode.window.showInformationMessage(`Inspected token type: ${tokenTypeKey}`); 20 | -------------------------------------------------------------------------------- /test/samples/Snake.m: -------------------------------------------------------------------------------- 1 | classdef Snake < Animal 2 | methods 3 | function obj = Snake(name, years) 4 | obj@Animal(name, years * 18, "Serpentes", true); 5 | end 6 | function obj = noise(obj) 7 | disp("Hiss"); 8 | end 9 | function obj = move(obj, meters) 10 | if meters < 2 11 | disp("Whip strike!"); 12 | else 13 | disp("Slithering.."); 14 | end 15 | move@Animal(obj, meters / 5); 16 | end 17 | function obj = sleep(obj, multiplier) 18 | disp("Hibernating.."); 19 | sleep@Animal(obj, multiplier * 3); 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/samples/Cat.m: -------------------------------------------------------------------------------- 1 | classdef Cat < Animal 2 | methods 3 | function obj = Cat(name, years) 4 | obj@Animal(name, years * 15, "Felidae", true); 5 | end 6 | function obj = noise(obj) 7 | handleCatNoiseKo(obj.Locale) 8 | end 9 | function obj = move(obj, meters) 10 | disp("Pussyfooting.."); 11 | move@Animal(obj, meters / 2); 12 | end 13 | function obj = sleep(obj, multiplier) 14 | disp("Napping.."); 15 | sleep@Animal(obj, multiplier * 16); 16 | end 17 | end 18 | end 19 | 20 | %% Test header folding and level=0&line!=0 21 | function handleCatNoiseKo(locale) 22 | if strcmp(locale, "ko"); disp("Yaong"); else; disp("Meow"); end 23 | end 24 | -------------------------------------------------------------------------------- /test/samples/Horse.m: -------------------------------------------------------------------------------- 1 | classdef Horse < Animal 2 | methods 3 | function obj = Horse(name, years) 4 | obj@Animal(name, years * 6, "Equidae", true); 5 | end 6 | function obj = noise(obj) 7 | disp("Neigh"); 8 | end 9 | function obj = move(obj, meters) 10 | if meters > 8 11 | disp("Galloping.."); 12 | elseif meters > 4 13 | disp("Cantering.."); 14 | else 15 | disp("Trotting.."); 16 | end 17 | move@Animal(obj, meters * 5); 18 | end 19 | function obj = sleep(obj, multiplier) 20 | disp("Napping.."); 21 | sleep@Animal(obj, multiplier * 16); 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoredRepositories": [ 3 | "test/extensions/vscode-matlab", 4 | "test/extensions/vscode-matlab/syntaxes/MATLAB-Language-grammar", 5 | "builds/0/project-0", 6 | "builds/0/project-0/test/extensions/vscode-matlab" 7 | ], 8 | "search.exclude": { 9 | "test/extensions/vscode-matlab/**": true 10 | }, 11 | "files.exclude": { 12 | ".vscode-test": true, 13 | ".vscode-test-web": true 14 | }, 15 | "material-icon-theme.files.associations": { 16 | "webpack.config.web.test.js": "webpack", 17 | "*.wiki": "document" 18 | }, 19 | "material-icon-theme.folders.associations": { 20 | "parser": "functions", 21 | "services": "resource", 22 | "scopes": "context", 23 | "definitions": "typescript" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/context/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | import { MockExtensionContext } from './mock'; 5 | 6 | export function setupEnvironmentForLanguageId(languageId: string): void { 7 | globalThis.languageId = languageId; 8 | switch (languageId) { 9 | case 'matlab': 10 | globalThis.extensionContext = new MockExtensionContext(languageId); 11 | break; 12 | case 'mediawiki': 13 | globalThis.extensionContext = void 0 as unknown as vscode.ExtensionContext; 14 | break; 15 | case 'typescript': 16 | globalThis.extensionContext = new MockExtensionContext(languageId); 17 | break; 18 | case 'vue': 19 | globalThis.extensionContext = void 0 as unknown as vscode.ExtensionContext; 20 | break; 21 | default: 22 | break; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | /** @type {webpack.Configuration} */ 5 | const configuration = { 6 | mode: 'none', 7 | target: 'node', 8 | entry: { 9 | 'src/main': './src/main.ts' 10 | }, 11 | output: { 12 | globalObject: 'globalThis', 13 | library: 'TextmateLanguageService', 14 | libraryTarget: 'umd', 15 | path: path.join(__dirname, 'dist') 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.js'], 19 | mainFields: ['module', 'main'] 20 | }, 21 | module: { 22 | rules: [ 23 | { test: /\.ts$/, loader: 'ts-loader' }, 24 | { test: /\.wasm$/, type: 'javascript/auto', loader: 'encoded-uint8array-loader' } 25 | ] 26 | }, 27 | externals: { 28 | 'vscode': 'commonjs vscode', 29 | 'crypto': 'commonjs crypto' 30 | } 31 | }; 32 | 33 | module.exports = configuration; 34 | -------------------------------------------------------------------------------- /test/harness-electron.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as path from 'path'; 4 | import { runTests as runTestsInElectron } from '@vscode/test-electron'; 5 | 6 | async function main() { 7 | try { 8 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../..'); 9 | const extensionTestsPath = path.resolve(__dirname, './runner-electron.js'); 10 | const launchArgs = [ 11 | '--disable-extensions=1', 12 | '--disable-gpu', 13 | '--disable-workspace-trust', 14 | '--no-xshm', 15 | extensionDevelopmentPath 16 | ]; 17 | // Node environment. 18 | await runTestsInElectron({ 19 | extensionDevelopmentPath, 20 | extensionTestsPath, 21 | launchArgs 22 | }); 23 | } catch (_) { 24 | // eslint-disable-next-line no-console 25 | console.error('Failed to run tests'); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | void main(); 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | concurrency: vscode_environment 4 | 5 | on: 6 | push: 7 | branches: [ release-candidate/* ] 8 | pull_request: 9 | branches: [ main, release-candidate/* ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | submodules: recursive 20 | - name: Use Node 20 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: '20' 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Run tests 27 | run: xvfb-run --server-args "-screen 0 1920x1080x24" npm test 28 | - name: Upload Data Artifact 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: vscode-textmate-languageservice-test-data 32 | path: test/data 33 | -------------------------------------------------------------------------------- /typings/token-information.api.d.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | declare module 'vscode' { 6 | 7 | // https://github.com/microsoft/vscode/issues/91555 8 | 9 | export enum StandardTokenType { 10 | Other = 0, 11 | Comment = 1, 12 | String = 2, 13 | RegEx = 3 14 | } 15 | 16 | export interface TokenInformation { 17 | type: StandardTokenType; 18 | range: Range; 19 | } 20 | 21 | export namespace languages { 22 | /** @deprecated */ 23 | export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/util/crypto.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import type * as c from 'crypto'; 5 | type NodeCrypto = typeof c; 6 | 7 | // The API for runtime detection is frankly not sane. 8 | // This is the best way to detect if we are in a web runtime. 9 | // microsoft/vscode#104436; microsoft/vscode#134568 10 | const isWebUI = vscode.env.uiKind === vscode.UIKind.Web; 11 | const isRemote = typeof vscode.env.remoteName === 'string'; 12 | const isWebRuntime = isWebUI && !isRemote; 13 | 14 | // Fail safe global object reference from within web extension worker. 15 | const w: typeof globalThis | void = isWebRuntime ? globalThis : void 0; 16 | 17 | // Export the web crypto global (for webworker + secure context runtimes). 18 | export const web: Crypto | void = w && w.crypto || void 0; 19 | 20 | // Export the node crypto module (for Node runtimes). 21 | export const node: NodeCrypto | void = !w ? require('crypto') as typeof c : void 0; 22 | -------------------------------------------------------------------------------- /test/harness-web.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as path from 'path'; 4 | import { runTests as runTestsInBrowser } from '@vscode/test-web'; 5 | 6 | import type { BrowserType } from '@vscode/test-web'; 7 | 8 | async function main() { 9 | try { 10 | const extensionDevelopmentPath = path.resolve(__dirname, '../../../..'); 11 | const extensionTestsPath = path.resolve(__dirname, './runner-web.js'); 12 | const extensionPaths = []; 13 | const browserType: BrowserType = 'chromium'; 14 | const port = 8080; 15 | const headless = true; 16 | const devTools = false; 17 | // Web environment. 18 | await runTestsInBrowser({ 19 | browserType, 20 | devTools, 21 | extensionDevelopmentPath, 22 | extensionPaths, 23 | extensionTestsPath, 24 | headless, 25 | port 26 | }); 27 | } catch (e) { 28 | // eslint-disable-next-line no-console 29 | console.error('Failed to run tests'); 30 | throw e; 31 | } 32 | } 33 | 34 | void main(); 35 | -------------------------------------------------------------------------------- /test/samples/Pet.ts: -------------------------------------------------------------------------------- 1 | class Pet { 2 | public locale: string; 3 | public name: string; 4 | public order: string; 5 | public age: number; 6 | public tameable: boolean; 7 | public constructor(name: string, age: number, order: string, tameable: boolean) { 8 | this.locale = Intl.DateTimeFormat().resolvedOptions().locale; 9 | this.name = name; 10 | this.order = order; 11 | this.age = age; 12 | this.tameable = tameable; 13 | } 14 | public copy(): Required> { 15 | return JSON.parse(JSON.stringify(this)); 16 | } 17 | public noise(hours: number): void { 18 | console.log("Grunt"); 19 | } 20 | public move(meters: number): void { 21 | console.log(`${this.name} moved ${meters.toString()}m.`); 22 | } 23 | public sleep(hours: number): void { 24 | console.log(`${this.name} slept for ${hours.toString()}.`); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/samples/Animal.m: -------------------------------------------------------------------------------- 1 | classdef Animal < matlab.mixin.Copyable 2 | properties 3 | Locale char 4 | Name string 5 | Order string 6 | Age int8 7 | Tameable logical 8 | end 9 | methods 10 | function obj = Animal(name, age, order, tameable) 11 | obj.Locale = char(regexp(get(0, 'Language'), '^[a-zA-Z]+', 'match')); 12 | obj.Name = name; 13 | obj.Order = order; 14 | obj.Age = age; 15 | obj.Tameable = tameable; 16 | end 17 | function obj = copy(obj) 18 | obj = copy@matlab.mixin.Copyable(obj); 19 | end 20 | function obj = noise(obj) 21 | disp("Grunt"); 22 | end 23 | function obj = move(obj, meters) 24 | disp([obj.Name " moved " num2str(meters) "m."]); 25 | end 26 | function obj = sleep(obj, hours) 27 | disp([obj.Name " slept for " num2str(hours) "hrs."]); 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "code-block-style": { 3 | "style": "fenced" 4 | }, 5 | "code-fence-style": { 6 | "style": "backtick" 7 | }, 8 | "emphasis-style": { 9 | "style": "asterisk" 10 | }, 11 | "fenced-code-language": false, 12 | "heading-style": { 13 | "style": "atx" 14 | }, 15 | "hr-style": { 16 | "style": "---" 17 | }, 18 | "line-length": false, 19 | "no-blanks-blockquote": false, 20 | "no-duplicate-heading": { 21 | "siblings_only": true 22 | }, 23 | "no-hard-tabs": false, 24 | "no-inline-html": false, 25 | "ol-prefix": { 26 | "style": "ordered" 27 | }, 28 | "proper-names": { 29 | "code_blocks": false, 30 | "names": [ 31 | "CommonMark", 32 | "Ctrl", 33 | "JavaScript", 34 | "Markdown", 35 | "markdown-it", 36 | "markdownlint", 37 | "Node.js", 38 | "Shift", 39 | "Visual Studio Code" 40 | ] 41 | }, 42 | "strong-style": { 43 | "style": "asterisk" 44 | }, 45 | "ul-style": { 46 | "style": "dash" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "⚡️️ Feature Request" 3 | about: "Suggest an idea for this project ✨" 4 | labels: [type-feature] 5 | 6 | --- 7 | 8 | ## ⚡️️ Feature Request 9 | 10 | - [ ] I'm using the latest version of `vscode-textmate-languageservice` available. 11 | - [ ] I searched [existing issues][vsctmls-issues], open & closed. Yes, my feature request is new. 12 | 13 | ### Is your feature request related to a problem? 14 | 15 | - **As a:** 16 | - **I want to:** 17 | - **So that:** 18 | 19 | ### Describe the solution you'd like 20 | 21 | 22 | ### Describe alternatives you've considered 23 | 24 | 25 | 26 | [vsctmls-issues]: https://github.com/vsce-toolroom/vscode-textmate-languageservice/issues?q=is%3Aopen+sort%3Aupdated-desc 27 | -------------------------------------------------------------------------------- /test/types/service-tests.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import TextmateLanguageService from '../..'; 3 | 4 | const TextmateScopeSelector = TextmateLanguageService.utils.TextmateScopeSelector; 5 | 6 | // $ExpectType TextmateScopeSelector 7 | const commentScopeSelector = new TextmateScopeSelector('comment'); 8 | 9 | // $ExpectType boolean 10 | const result = commentScopeSelector.match(['source.ts', 'comment.line.double-slash.ts']); 11 | if (result === true) { 12 | void vscode.window.showInformationMessage('TS comment matching OK'); 13 | } 14 | 15 | // $ExpectType TextmateLanguageService 16 | const typescriptService = new TextmateLanguageService('typescript'); 17 | 18 | // $ExpectType TokenizerService 19 | const textmateTokenService = await typescriptService.initTokenService(); 20 | 21 | const textDocument = vscode.window.activeTextEditor.document; 22 | 23 | // $ExpectType TextmateToken[] 24 | const tokens = await textmateTokenService.fetch(textDocument); 25 | 26 | if (tokens[tokens.length - 1].line === (textDocument.lineCount - 1)) { 27 | void vscode.window.showInformationMessage('Tokenization OK'); 28 | } 29 | -------------------------------------------------------------------------------- /src/i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "cs": { 3 | "plainText.alias": "Prostý text" 4 | }, 5 | "de": { 6 | "plainText.alias": "Nur-Text" 7 | }, 8 | "es": { 9 | "plainText.alias": "Texto sin formato" 10 | }, 11 | "fr": { 12 | "plainText.alias": "Texte brut" 13 | }, 14 | "it": { 15 | "plainText.alias": "Testo normale" 16 | }, 17 | "ja": { 18 | "plainText.alias": "プレーンテキスト" 19 | }, 20 | "ko": { 21 | "plainText.alias": "일반 텍스트" 22 | }, 23 | "pl": { 24 | "plainText.alias": "Zwykły tekst" 25 | }, 26 | "pt": { 27 | "plainText.alias": "Texto sem Formatação" 28 | }, 29 | "pt-BR": { 30 | "plainText.alias": "Texto sem Formatação" 31 | }, 32 | "qps": { 33 | "plainText.alias": "Plæïñ Tëxt" 34 | }, 35 | "qps-ploc": { 36 | "plainText.alias": "Plæïñ Tëxt" 37 | }, 38 | "ru": { 39 | "plainText.alias": "Простой текст" 40 | }, 41 | "tr": { 42 | "plainText.alias": "Düz Metin" 43 | }, 44 | "zh": { 45 | "plainText.alias": "純文字" 46 | }, 47 | "zh-hans": { 48 | "plainText.alias": "纯文本" 49 | }, 50 | "zh-hant": { 51 | "plainText.alias": "純文字" 52 | } 53 | } -------------------------------------------------------------------------------- /test/extensions/vscode-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "displayName": "TypeScript", 4 | "description": "TypeScript language service wiring for Textmate language service.", 5 | "version": "0.0.1", 6 | "publisher": "sndst00m", 7 | "engines": { 8 | "vscode": "^1.51.1" 9 | }, 10 | "categories": [ 11 | "Programming Languages" 12 | ], 13 | "activationEvents": [ 14 | "onLanguage:typescript" 15 | ], 16 | "main": "./out/src/extension.js", 17 | "browser": "./out/src/extension.js", 18 | "contributes": {}, 19 | "textmate-languageservice-contributes": {}, 20 | "scripts": { 21 | "vscode:prepublish": "npm run compile", 22 | "compile": "tsc -p ./", 23 | "watch": "tsc -watch -p ./", 24 | "pretest": "npm run compile", 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | }, 27 | "devDependencies": { 28 | "@types/vscode": "^1.55.0", 29 | "@vscode/test-electron": "^2.3.6", 30 | "@vscode/test-web": "^0.0.63", 31 | "mocha": "^10.2.0", 32 | "typescript": "^4.9.5" 33 | }, 34 | "dependencies": { 35 | "vscode-textmate-languageservice": "^4.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webpack.config.web.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | /** @type {webpack.Configuration} */ 5 | const configuration = { 6 | mode: 'none', 7 | target: 'webworker', 8 | entry: { 9 | 'test/runner-web': './test/runner-web.ts', 10 | }, 11 | output: { 12 | globalObject: 'globalThis', 13 | libraryTarget: 'commonjs', 14 | path: path.join(__dirname, 'dist') 15 | }, 16 | resolve: { 17 | extensions: ['.ts', '.js'], 18 | fallback: { 19 | crypto: false, 20 | path: require.resolve('path-browserify') 21 | }, 22 | mainFields: ['browser', 'module', 'main'] 23 | }, 24 | module: { 25 | rules: [ 26 | { test: /\.ts$/, loader: 'ts-loader', options: { configFile: 'test/tsconfig.json' } }, 27 | { test: /\.wasm$/, type: 'javascript/auto', loader: 'encoded-uint8array-loader' } 28 | ] 29 | }, 30 | plugins: [ 31 | new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }), 32 | new webpack.ProvidePlugin({ process: 'process/browser' }) 33 | ], 34 | externals: { 35 | 'vscode': 'commonjs vscode', 36 | 'crypto': 'commonjs crypto' 37 | } 38 | }; 39 | 40 | module.exports = configuration; 41 | -------------------------------------------------------------------------------- /src/util/fast-selector.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export class FastScopeSelector { 4 | private _cache: Record = {}; 5 | 6 | constructor(public readonly source: string) {} 7 | 8 | public matches(scopes: string | string[]) { 9 | if (typeof scopes === 'string') { 10 | scopes = [scopes]; 11 | } 12 | const target = scopes.join(' '); 13 | const entry = this._cache[target]; 14 | 15 | if (typeof entry !== 'undefined') { 16 | return entry; 17 | } else { 18 | const position = target.indexOf(this.source); 19 | if (position === -1) { 20 | return (this._cache[target] = false); 21 | } 22 | const left = target.charAt(position - 1); 23 | const right = target.charAt(position + this.source.length); 24 | 25 | const isScopeBoundary = (c: string) => ['', '.', ' '].includes(c); 26 | return (this._cache[target] = [left, right].every(isScopeBoundary)); 27 | } 28 | } 29 | 30 | public getPrefix(_: string | string[]): undefined { 31 | return void 0; 32 | } 33 | 34 | public getPriority(_: string | string[]): undefined { 35 | return void 0; 36 | } 37 | 38 | public toString(): string { 39 | return this.source; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/util/lazy.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | export interface Lazy { 8 | readonly value: T; 9 | readonly hasValue: boolean; 10 | map(f: (x: T) => R): Lazy; 11 | } 12 | 13 | class LazyValue implements Lazy { 14 | private _hasValue = false; 15 | private _value?: T; 16 | 17 | constructor(private readonly _getValue: () => T) { } 18 | 19 | public get value(): T { 20 | if (!this._hasValue) { 21 | this._hasValue = true; 22 | this._value = this._getValue(); 23 | } 24 | return this._value; 25 | } 26 | 27 | public get hasValue(): boolean { 28 | return this._hasValue; 29 | } 30 | 31 | public map(f: (x: T) => R): Lazy { 32 | return new LazyValue(() => f(this.value)); 33 | } 34 | } 35 | 36 | export function lazy(getValue: () => T): Lazy { 37 | return new LazyValue(getValue); 38 | } 39 | -------------------------------------------------------------------------------- /test/util/files.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { getTestModeExtension } from './common'; 6 | 7 | export const LANGUAGES = ['matlab', 'typescript', 'mediawiki', 'vue']; 8 | 9 | export const EXTENSIONS = ['.m', '.ts', '.wiki', '.vue']; 10 | 11 | export const BASENAMES = { 12 | matlab: ['Animal', 'Cat', 'Dog', 'Horse', 'Snake'], 13 | mediawiki: ['List'], 14 | typescript: ['Pet'], 15 | vue: ['Hello'] 16 | }; 17 | 18 | /** 19 | * @param {string} basename Basename of sample file - `test\samples\*.m`. 20 | */ 21 | export function getSampleFileUri(basename: string): vscode.Uri { 22 | const ext = EXTENSIONS[LANGUAGES.indexOf(globalThis.languageId)]; 23 | return vscode.Uri.joinPath(getTestModeExtension().extensionUri, `./samples/${basename}${ext}`); 24 | } 25 | 26 | /** 27 | * @param {string} component Name of callee test object component - `src\*.js`. 28 | * @param {string} sample Basename of target sample file - `data\*\*\*.json`. 29 | * */ 30 | export function getComponentSampleDataUri(component: string, sample: string): vscode.Uri { 31 | return vscode.Uri.joinPath(getTestModeExtension().extensionUri, `./data/${component}/${sample}.json`); 32 | } 33 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 🏗️ Pull Request 2 | 3 | 4 | - [ ] Used a clear / meaningful title for this pull request. 5 | - [ ] Tested the changes on your own grammar (on your projects). 6 | - [ ] Added / Edited tests to reflect changes (`test` folder). 7 | - [ ] Have read the **Contributing** part of the **README**. 8 | - [ ] Passed `npm test`. 9 | - [ ] I noted my changes in the pull request body and/or changelog. 10 | 11 | 12 | 13 | #### 👷🏾‍♀️ Fixes 14 | 15 | 16 | 17 | #### Description 18 | 19 | 20 | 21 | #### What changes have you made? 22 | 23 | - ... 24 | 25 | #### What tests have you completed? 26 | 27 | - [ ] Tested this in Visual Studio Code v. 28 | - [ ] Tested this in Visual Studio Code Insiders v. 29 | - [ ] Tested this for GitHub Codespaces v. 30 | - [ ] Tested this in VSCodium v. 31 | 32 | #### Anything else worth mentioning? 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /scripts/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const { Octokit } = require('octokit'); 5 | const fs = require('fs'); 6 | 7 | const octokit = new Octokit(); 8 | 9 | async function main() { 10 | let i18nJson = {}; 11 | 12 | const tree = await octokit.request('GET /repos/microsoft/vscode-loc/contents/i18n'); 13 | 14 | for (const { name } of tree.data) { 15 | const filepath = path.join('/microsoft/vscode-loc', `main/i18n/${name}`, '/translations/main.i18n.json'); 16 | const url = new URL(filepath, `https://raw.githubusercontent.com/`); 17 | 18 | const raw = await fetch(url.toString()); 19 | const data = await raw.json(); 20 | 21 | const locale = name.replace('vscode-language-pack-', ''); 22 | const alias = data['contents']['vs/editor/common/languages/modesRegistry']['plainText.alias']; 23 | 24 | if (locale.includes('-')) { 25 | const base = locale.split('-')[0]; 26 | i18nJson[base] = {}; 27 | i18nJson[base]['plainText.alias'] = alias; 28 | } 29 | 30 | i18nJson[locale] = {}; 31 | i18nJson[locale]['plainText.alias'] = alias; 32 | } 33 | 34 | i18nJson = JSON.stringify(i18nJson, null, 2); 35 | fs.writeFileSync(path.resolve(__dirname, '..', 'src', 'i18n.json'), i18nJson); 36 | } 37 | 38 | void main(); 39 | -------------------------------------------------------------------------------- /src/util/dispose.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import type * as vscode from 'vscode'; 8 | 9 | export function disposeAll(disposables: vscode.Disposable[]) { 10 | while (disposables.length) { 11 | const item = disposables.pop(); 12 | if (item) { 13 | item.dispose(); 14 | } 15 | } 16 | } 17 | 18 | export abstract class Disposable { 19 | protected _disposables: vscode.Disposable[] = []; 20 | 21 | private _isDisposed = false; 22 | 23 | protected get isDisposed() { 24 | return this._isDisposed; 25 | } 26 | 27 | public dispose(): any { 28 | if (this._isDisposed) { 29 | return void 0; 30 | } 31 | this._isDisposed = true; 32 | disposeAll(this._disposables); 33 | } 34 | 35 | protected _register(value: T): T { 36 | if (this._isDisposed) { 37 | value.dispose(); 38 | } else { 39 | this._disposables.push(value); 40 | } 41 | return value; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/suite/providers/workspace-symbol.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { isWebRuntime } from '../../util/runtime'; 6 | import { workspaceSymbolProviderPromise } from '../../util/factory'; 7 | import { runSamplePass } from '../../util/bench'; 8 | import { BASENAMES } from '../../util/files'; 9 | 10 | suite('test/suite/workspace-symbol.test.ts - TextmateWorkspaceSymbolProvider class (src/workspace-symbol.ts)', function() { 11 | this.timeout(10000); 12 | 13 | test('TextmateWorkspaceSymbolProvider.provideWorkspaceSymbols(): Promise', async function() { 14 | // Early exit + pass if we are in web runtime or testing . 15 | if (isWebRuntime || BASENAMES[globalThis.languageId].length === 1) { 16 | this.skip(); 17 | } 18 | 19 | void vscode.window.showInformationMessage('TextmateWorkspaceSymbolProvider class (src/workspace-symbol.ts)'); 20 | const symbols = await workspaceSymbolProviderResult(); 21 | await runSamplePass('workspace-symbol', 'index', symbols); 22 | }); 23 | }); 24 | 25 | async function workspaceSymbolProviderResult() { 26 | const workspaceSymbolProvider = await workspaceSymbolProviderPromise; 27 | const symbols = await workspaceSymbolProvider.provideWorkspaceSymbols('obj.'); 28 | return symbols; 29 | } 30 | -------------------------------------------------------------------------------- /src/util/loader.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import JSONC from 'tiny-jsonc'; 5 | import * as i18n from '../i18n.json'; 6 | import type { JsonValue, PartialDeep } from 'type-fest'; 7 | 8 | // We assume that the document language is in UTF-8. 9 | const decoder = new TextDecoder('utf-8'); 10 | 11 | export async function readFileText(uri: vscode.Uri): Promise { 12 | try { 13 | return decoder.decode(await vscode.workspace.fs.readFile(uri)); 14 | } catch (e) { 15 | throw e; 16 | } 17 | }; 18 | 19 | type PartialJsonValue = PartialDeep; 20 | 21 | export async function loadJsonFile(uri: vscode.Uri, fallback?: string): Promise { 22 | try { 23 | const text = await readFileText(uri); 24 | return JSONC.parse(text) as T; 25 | } catch (e) { 26 | if (fallback) { 27 | return JSONC.parse(fallback) as T; 28 | } 29 | 30 | if (e && typeof (e as Error).stack === 'string') { 31 | (e as Error).stack += `\n in ${uri.path}`; 32 | } 33 | 34 | throw e; 35 | } 36 | }; 37 | 38 | export function loadMessageBundle() { 39 | return function(key: string, message: string): string { 40 | const locale = vscode.env.language; 41 | const base = locale.split('-')[0]; 42 | return (i18n[locale] || i18n[base] || {})[key] || message; 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /assets/compatibility-badge.svg: -------------------------------------------------------------------------------- 1 | Compatibility: >=v1.51.0Compatibility>=v1.55.0 2 | -------------------------------------------------------------------------------- /scripts/stage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const shelljs = require('shelljs'); 4 | const path = require('path'); 5 | const glob = require('glob'); 6 | 7 | const extensionPath = process.argv[2]; 8 | const extensionTestDevDependencies = [ 9 | 'mocha', 10 | '@vscode/test-electron', 11 | '@vscode/test-web' 12 | ]; 13 | const extensionTestDataFolders = ['data', 'samples']; 14 | 15 | shelljs.exec('npm pack'); 16 | const tarballPath = path.basename(glob.globSync('*.tgz')[0]); 17 | shelljs.exec(`npm install --prefix ${extensionPath} --omit=dev ${tarballPath}`); 18 | 19 | const packageJSON = require('../package.json'); 20 | 21 | for (let index = 0; index < extensionTestDevDependencies.length; index++) { 22 | /** @type {keyof typeof packageJSON.devDependencies} */ 23 | const packageName = extensionTestDevDependencies[index]; 24 | const packageVersion = packageJSON.devDependencies[packageName]; 25 | const testDependency = `${packageName}@${packageVersion}`; 26 | shelljs.exec(`npm install --prefix ${extensionPath} --save-dev --package-lock false ${testDependency}`); 27 | } 28 | 29 | for (let index = 0; index < extensionTestDataFolders.length; index++) { 30 | const dataDir = extensionTestDataFolders[index]; 31 | shelljs.mkdir(`${extensionPath}/${dataDir}`); 32 | shelljs.cp('-r', `./test/${dataDir}/*`, `${extensionPath}/${dataDir}`); 33 | } 34 | 35 | shelljs.exec(`npm exec --prefix ${extensionPath} playwright install`); 36 | -------------------------------------------------------------------------------- /test/context/variables.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | 5 | export class MockEnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { 6 | private readonly map: Map = new Map(); 7 | private _persistent = true; 8 | constructor(serialized?: Array<[string, vscode.EnvironmentVariableMutator]>) { 9 | this.map = new Map(serialized); 10 | } 11 | public get size(): number { 12 | return this.map.size; 13 | } 14 | public get persistent(): boolean { 15 | return this._persistent; 16 | } 17 | public set persistent(value: boolean) { 18 | this._persistent = value; 19 | } 20 | public replace(_1: string, _2: string): void { 21 | return void 0; 22 | } 23 | public append(_1: string, _2: string): void { 24 | return void 0; 25 | } 26 | public prepend(_1: string, _2: string): void { 27 | return void 0; 28 | } 29 | public get(variable: string): vscode.EnvironmentVariableMutator | undefined { 30 | return this.map.get(variable); 31 | } 32 | public forEach(_1: Parameters[0], _2?: any): void { 33 | return void 0; 34 | } 35 | public [Symbol.iterator](): IterableIterator<[variable: string, mutator: vscode.EnvironmentVariableMutator]> { 36 | return this.map.entries(); 37 | } 38 | public delete(_1: string): void { 39 | return void 0; 40 | } 41 | public clear(): void { 42 | return void 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/scopes/parser.pegjs: -------------------------------------------------------------------------------- 1 | // Copyright (c) GitHub Inc. All rights reserved. 2 | // Licensed under the MIT License. See LICENSE.md in the project root for license information. 3 | start = _ selector:(selector) _ { 4 | return selector; 5 | } 6 | 7 | atom 8 | = _ segment:([a-zA-Z0-9+_]+[a-zA-Z0-9-+_]*) _ { 9 | return new matchers.SegmentMatcher(segment); 10 | } 11 | 12 | / _ asterisk:[\*] _ { 13 | return new matchers.TrueMatcher(); 14 | } 15 | 16 | scope 17 | = first:atom others:("." atom)* { 18 | return new matchers.ScopeMatcher(first, others); 19 | } 20 | 21 | path 22 | = prefix:([LRB]":")? first:scope others:(_ scope)* { 23 | return new matchers.PathMatcher(prefix, first, others); 24 | } 25 | 26 | group 27 | = prefix:([LRB]":")? "(" _ selector:selector _ ")" { 28 | return new matchers.GroupMatcher(prefix, selector); 29 | } 30 | 31 | expression 32 | = "-" _ group:group _ { 33 | return new matchers.NegateMatcher(group); 34 | } 35 | 36 | / "-" _ path:path _ { 37 | return new matchers.NegateMatcher(path); 38 | } 39 | 40 | / group 41 | 42 | / path 43 | 44 | composite 45 | = left:expression _ operator:[|&-] _ right:composite { 46 | return new matchers.CompositeMatcher(left, operator, right); 47 | } 48 | 49 | / expression 50 | 51 | selector 52 | = left:composite _ "," _ right:selector? { 53 | if (right) { 54 | return new matchers.OrMatcher(left, right); 55 | } else { 56 | return left; 57 | } 58 | } 59 | 60 | / composite 61 | 62 | _ 63 | = [ \t]* 64 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/syntaxes/vue-sfc-style-variable-injection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "fileTypes": [], 4 | "injectionSelector": "L:source.css -comment, L:source.postcss -comment, L:source.sass -comment, L:source.stylus -comment", 5 | "patterns": [ 6 | { 7 | "include": "#vue-sfc-style-variable-injection" 8 | } 9 | ], 10 | "repository": { 11 | "vue-sfc-style-variable-injection": { 12 | "begin": "\\b(v-bind)\\s*\\(", 13 | "name": "vue.sfc.style.variable.injection.v-bind", 14 | "end": "\\)", 15 | "beginCaptures": { 16 | "1": { 17 | "name": "entity.name.function" 18 | } 19 | }, 20 | "patterns": [ 21 | { 22 | "begin": "('|\")", 23 | "beginCaptures": { 24 | "1": { 25 | "name": "punctuation.definition.tag.begin.html" 26 | } 27 | }, 28 | "end": "(\\1)", 29 | "endCaptures": { 30 | "1": { 31 | "name": "punctuation.definition.tag.end.html" 32 | } 33 | }, 34 | "name": "source.ts.embedded.html.vue", 35 | "patterns": [ 36 | { 37 | "include": "source.js" 38 | } 39 | ] 40 | }, 41 | { 42 | "include": "source.js" 43 | } 44 | ] 45 | } 46 | }, 47 | "scopeName": "vue.sfc.style.variable.injection" 48 | } 49 | -------------------------------------------------------------------------------- /src/util/oniguruma.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /* -------------------------------------------------------------------------------------------- 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 5 | * -------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import * as vscodeOniguruma from 'vscode-oniguruma'; 9 | import type * as vscodeTextmate from 'vscode-textmate'; 10 | 11 | // Use webpack + encoded-uint8array-loader to generate a `Uint8Array` WASM module. 12 | // This is not streaming :[ but vscode libs must bundle WASM deps to support web ecosystem. 13 | // Extension alternative is using copy-webpack-plugin + fetch to include the WASM file. 14 | // TODO: use data URI and native node 18.x fetch for streaming compilation. 15 | import * as data from '../../node_modules/vscode-oniguruma/release/onig.wasm'; 16 | 17 | let onigurumaLib: vscodeTextmate.IOnigLib | null = null; 18 | 19 | export async function getOniguruma(): Promise { 20 | if (onigurumaLib) { 21 | return onigurumaLib; 22 | } 23 | await vscodeOniguruma.loadWASM({ data }); 24 | onigurumaLib = { 25 | createOnigScanner(patterns: string[]) { 26 | return new vscodeOniguruma.OnigScanner(patterns); 27 | }, 28 | createOnigString(str: string) { 29 | return new vscodeOniguruma.OnigString(str); 30 | } 31 | }; 32 | return onigurumaLib; 33 | } 34 | -------------------------------------------------------------------------------- /test/util/common.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import TextmateLanguageService from '../../src/main'; 6 | 7 | import type { JsonValue, PartialDeep } from 'type-fest'; 8 | 9 | const encoder = new TextEncoder(); 10 | 11 | export type PartialJsonValue = PartialDeep; 12 | 13 | export function getTestModeExtension(): vscode.Extension { 14 | const last = vscode.extensions.all[vscode.extensions.all.length - 1]; 15 | if (!last.id || last.id.split('.')[0] === 'vscode') { 16 | throw new Error('No test mode extension found.'); 17 | } 18 | return last; 19 | } 20 | 21 | export async function writeJsonFile(uri: vscode.Uri, json: PartialJsonValue): Promise { 22 | try { 23 | const text = JSON.stringify(json, null, 2) + '\n'; 24 | const bytes = encoder.encode(text); 25 | await vscode.workspace.fs.writeFile(uri, bytes); 26 | } catch (e) { 27 | if (e && typeof (e as Error).stack === 'string') { 28 | (e as Error).stack += `\n in ${uri.path}`; 29 | } 30 | throw e; 31 | } 32 | } 33 | 34 | /** `loadJsonFile` utility. */ 35 | export const loadJsonFile = TextmateLanguageService.utils.loadJsonFile; 36 | 37 | /** `ContributorData` utility. */ 38 | export const ContributorData = TextmateLanguageService.utils.ContributorData; 39 | 40 | /** `TextmateScopeSelector` utility. */ 41 | export const TextmateScopeSelector = TextmateLanguageService.utils.TextmateScopeSelector; 42 | 43 | /** `TextmateScopeSelectorMap` utility. */ 44 | export const TextmateScopeSelectorMap = TextmateLanguageService.utils.TextmateScopeSelectorMap; 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: "Something isn't working well 🚨" 4 | labels: [type-bug] 5 | 6 | --- 7 | 8 | ## 🐛 Bug Report 9 | 10 | - [ ] I'm using the latest version of `vscode-textmate-languageservice` available. 11 | - [ ] I searched [existing issues][vsctmls-issues], open & closed. Yes, my issue is new. 12 | 13 | ### Describe the bug 14 | 15 | 16 | 17 | ### Reproduction / log 18 | 19 | ``` 20 | A MVCE (Minimum Verifiable Complete Example) or error log of the problem. 21 | ``` 22 | 23 | 24 | 25 | ### Screenshot / blame 26 | 27 | 28 | 29 | #### Expected behavior 30 | 31 | 32 | 33 | ### Possible solution 34 | 35 | 36 | 37 | ### Additional context 38 | 39 | 40 | 41 | - VS Code version: 42 | - Dependent extension ID: 43 | - OS version: 44 | 45 | 46 | [vsctmls-issues]: https://github.com/vsce-toolroom/vscode-textmate-languageservice/issues?q=is%3Aopen+sort%3Aupdated-desc 47 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mediawiki", 3 | "displayName": "MediaWiki", 4 | "description": "MediaWiki token service wiring for Textmate language service.", 5 | "version": "0.0.1", 6 | "publisher": "sndst00m", 7 | "engines": { 8 | "vscode": "^1.51.1" 9 | }, 10 | "categories": [ 11 | "Programming Languages" 12 | ], 13 | "activationEvents": [ 14 | "onLanguage:typescript" 15 | ], 16 | "main": "./out/src/extension.js", 17 | "browser": "./out/src/extension.js", 18 | "contributes": {}, 19 | "textmate-languageservice-contributes": { 20 | "languages": [ 21 | { 22 | "id": "mediawiki", 23 | "aliases": [ 24 | "MediaWiki" 25 | ], 26 | "extensions": [ 27 | ".mediawiki", 28 | ".mw", 29 | ".wiki", 30 | ".wikitext", 31 | ".wt" 32 | ], 33 | "configuration": "./language-configuration.json" 34 | } 35 | ], 36 | "grammars": [ 37 | { 38 | "language": "mediawiki", 39 | "scopeName": "text.html.mediawiki", 40 | "path": "./syntaxes/mediawiki.tmLanguage.json" 41 | } 42 | ] 43 | }, 44 | "scripts": { 45 | "vscode:prepublish": "npm run compile", 46 | "compile": "tsc -p ./", 47 | "watch": "tsc -watch -p ./", 48 | "pretest": "npm run compile", 49 | "test": "echo \"Error: no test specified\" && exit 1" 50 | }, 51 | "devDependencies": { 52 | "@types/vscode": "^1.55.0", 53 | "@vscode/test-electron": "^2.3.6", 54 | "@vscode/test-web": "^0.0.63", 55 | "mocha": "^10.2.0", 56 | "typescript": "^4.9.5" 57 | }, 58 | "dependencies": { 59 | "vscode-textmate-languageservice": "^4.0.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/suite/services/outline.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { documentServicePromise, outlineServicePromise } from '../../util/factory'; 6 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 7 | import { runSamplePass } from '../../util/bench'; 8 | 9 | import type { OutlineEntry } from '../../../src/services/outline'; 10 | 11 | suite('test/suite/outline.test.ts - OutlineService class (src/services/outline.ts)', function() { 12 | this.timeout(5000); 13 | 14 | test('OutlineService.fetch(): Promise', async function() { 15 | void vscode.window.showInformationMessage('OutlineService class (src/services/outline.ts)'); 16 | const { outputs, samples } = await outlineServiceOutput(); 17 | 18 | let error: TypeError | void = void 0; 19 | for (let index = 0; index < samples.length; index++) { 20 | const basename = BASENAMES[globalThis.languageId][index]; 21 | const outline = outputs[index]; 22 | 23 | try { 24 | await runSamplePass('outline', basename, outline); 25 | } catch (e) { 26 | error = typeof error !== 'undefined' ? error : e as Error; 27 | } 28 | } 29 | if (error) { 30 | throw error; 31 | } 32 | }); 33 | }); 34 | 35 | async function outlineServiceOutput() { 36 | const documentService = await documentServicePromise; 37 | const outlineService = await outlineServicePromise; 38 | 39 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 40 | const outputs: OutlineEntry[][] = []; 41 | 42 | for (const resource of samples) { 43 | const document = await documentService.getDocument(resource); 44 | const outline = await outlineService.fetch(document); 45 | 46 | outputs.push(outline); 47 | } 48 | 49 | return { outputs, samples }; 50 | } 51 | -------------------------------------------------------------------------------- /test/suite/services/tokenizer.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { documentServicePromise, tokenServicePromise } from '../../util/factory'; 6 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 7 | import { runSamplePass } from '../../util/bench'; 8 | 9 | import type { TextmateToken } from '../../../src/services/tokenizer'; 10 | 11 | suite('test/suite/tokenizer.test.ts - TokenizerService class (src/services/tokenizer.ts)', function() { 12 | this.timeout(5000); 13 | 14 | test('TokenizerService.fetch(): Promise', async function() { 15 | void vscode.window.showInformationMessage('TokenizerService class (src/services/tokenizer.ts)'); 16 | const { samples, outputs } = await tokenServiceOutput(); 17 | 18 | let error: TypeError | void = void 0; 19 | for (let index = 0; index < samples.length; index++) { 20 | const basename = BASENAMES[globalThis.languageId][index]; 21 | const tokens = outputs[index]; 22 | 23 | try { 24 | await runSamplePass('tokenizer', basename, tokens); 25 | } catch (e) { 26 | error = typeof error !== 'undefined' ? error : e as Error; 27 | } 28 | } 29 | if (error) { 30 | throw error; 31 | } 32 | }); 33 | }); 34 | 35 | async function tokenServiceOutput() { 36 | const documentService = await documentServicePromise; 37 | const tokenService = await tokenServicePromise; 38 | 39 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 40 | const outputs: TextmateToken[][] = []; 41 | 42 | for (const resource of samples) { 43 | const document = await documentService.getDocument(resource); 44 | const tokens = await tokenService.fetch(document); 45 | 46 | outputs.push(tokens); 47 | } 48 | 49 | return { outputs, samples }; 50 | } 51 | -------------------------------------------------------------------------------- /test/suite/providers/document-symbol.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { documentServicePromise, documentSymbolProviderPromise } from '../../util/factory'; 6 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 7 | import { runSamplePass } from '../../util/bench'; 8 | 9 | suite('test/suite/document-symbol.test.ts - TextmateDocumentSymbolProvider class (src/document-symbol.ts)', function() { 10 | this.timeout(10000); 11 | 12 | test('TextmateDocumentSymbolProvider.provideDocumentSymbols(): Promise', async function() { 13 | void vscode.window.showInformationMessage('TextmateDocumentSymbolProvider class (src/document-symbol.ts)'); 14 | const samples = await documentSymbolProviderResult(); 15 | 16 | let error: TypeError | void = void 0; 17 | for (let index = 0; index < samples.length; index++) { 18 | const basename = BASENAMES[globalThis.languageId][index]; 19 | const symbols = samples[index]; 20 | 21 | try { 22 | await runSamplePass('document-symbol', basename, symbols); 23 | } catch (e) { 24 | error = typeof error !== 'undefined' ? error : e as Error; 25 | } 26 | } 27 | if (error) { 28 | throw error; 29 | } 30 | }); 31 | }); 32 | 33 | async function documentSymbolProviderResult() { 34 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 35 | 36 | const documentService = await documentServicePromise; 37 | const documentSymbolProvider = await documentSymbolProviderPromise; 38 | const results: vscode.DocumentSymbol[][] = []; 39 | 40 | for (const resource of samples) { 41 | const document = await documentService.getDocument(resource); 42 | 43 | const symbols = await documentSymbolProvider.provideDocumentSymbols(document); 44 | 45 | results.push(symbols); 46 | } 47 | 48 | return results; 49 | } 50 | -------------------------------------------------------------------------------- /test/samples/Hello.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46 | 47 | 57 | 58 | 59 | 76 | 77 | 81 | -------------------------------------------------------------------------------- /test/runner-web.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { setupEnvironmentForLanguageId } from './context'; 6 | 7 | import { getTestModeExtension } from './util/common'; 8 | 9 | // import mocha for the browser, defining the `mocha` global 10 | import 'mocha/mocha'; 11 | 12 | export async function run(): Promise { 13 | void vscode.window.showInformationMessage('Start all tests.'); 14 | 15 | const languageId = getTestModeExtension().id.split('.')[1]; 16 | setupEnvironmentForLanguageId(languageId); 17 | 18 | 19 | if (languageId !== 'mediawiki') { 20 | await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); 21 | await vscode.languages.setTextDocumentLanguage(vscode.window.activeTextEditor.document, languageId); 22 | await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); 23 | } 24 | 25 | return new Promise((c, x) => { 26 | mocha.setup({ reporter: void 0, ui: 'tdd' }); 27 | 28 | // import mocha test files, so that webpack can inline them 29 | import('./suite/services/selectors.test'); 30 | import('./suite/services/tokenizer.test'); 31 | import('./suite/services/outline.test'); 32 | import('./suite/services/document.test'); 33 | import('./suite/providers/folding.test'); 34 | import('./suite/providers/definition.test'); 35 | import('./suite/providers/document-symbol.test'); 36 | import('./suite/providers/workspace-symbol.test'); 37 | import('./suite/api/token-information.test'); 38 | import('./suite/api/language-contribution.test'); 39 | 40 | try { 41 | // Run the mocha test 42 | mocha.run(failures => { 43 | if (failures > 0) { 44 | x(new Error(`${failures} tests failed.`)); 45 | } else { 46 | c(); 47 | } 48 | }); 49 | } catch (e) { 50 | // eslint-disable-next-line no-console 51 | console.error(e); 52 | x(e); 53 | } 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /test/runner-electron.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import * as Mocha from 'mocha'; 5 | 6 | import { setupEnvironmentForLanguageId } from './context'; 7 | import { getTestModeExtension } from './util/common'; 8 | 9 | const files = [ 10 | require.resolve('./suite/services/selectors.test'), 11 | require.resolve('./suite/services/tokenizer.test'), 12 | require.resolve('./suite/services/outline.test'), 13 | require.resolve('./suite/services/document.test'), 14 | require.resolve('./suite/providers/folding.test'), 15 | require.resolve('./suite/providers/definition.test'), 16 | require.resolve('./suite/providers/document-symbol.test'), 17 | require.resolve('./suite/providers/workspace-symbol.test'), 18 | require.resolve('./suite/api/token-information.test'), 19 | require.resolve('./suite/api/language-contribution.test') 20 | ]; 21 | 22 | export async function run(): Promise { 23 | void vscode.window.showInformationMessage('Start all tests.'); 24 | 25 | const mocha = new Mocha({ reporter: 'spec', ui: 'tdd' }); 26 | 27 | const languageId = getTestModeExtension().id.split('.')[1]; 28 | setupEnvironmentForLanguageId(languageId); 29 | 30 | if (languageId !== 'mediawiki') { 31 | await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); 32 | await vscode.languages.setTextDocumentLanguage(vscode.window.activeTextEditor.document, languageId); 33 | await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); 34 | } 35 | 36 | return new Promise((c, x) => { 37 | files.forEach(f => mocha.addFile(f)); 38 | 39 | try { 40 | mocha.run(function(failures) { 41 | if (failures > 0) { 42 | x(new Error(`${failures} tests failed.`)); 43 | } else { 44 | c(); 45 | } 46 | }); 47 | } catch (e) { 48 | // eslint-disable-next-line no-console 49 | console.error(e); 50 | x(e); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2023 Munin M. and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | vscode-textmate-languageservice incorporates [vscode-matlab](https://github.com/Gimly/vscode-matlab/tree/8ac2c2c/), 23 | Copyright (c) 2017-2021 by Gimly and contributors, https://github.com/Gimly/vscode-matlab/graphs/contributors. 24 | first-mate is distributed under the terms of the MIT License. 25 | 26 | vscode-textmate-languageservice incorporates [first-mate](https://github.com/atom/first-mate/tree/v7.4.2/src), 27 | Copyright (c) 2013-2021 GitHub Inc, https://github.com. 28 | first-mate is distributed under the terms of the MIT License. 29 | 30 | vscode-textmate-languageservice incorporates [vscode-textmate](https://github.com/microsoft/vscode-textmate/tree/v5.5.0/src), 31 | Copyright (c) 2013-2021 Microsoft Corporation, https://www.microsoft.com. 32 | first-mate is distributed under the terms of the MIT License. 33 | -------------------------------------------------------------------------------- /test/data/workspace-symbol/index.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "obj.Locale", 4 | "kind": "Variable", 5 | "location": { 6 | "uri": "./samples/Animal.m", 7 | "range": [ 8 | { 9 | "line": 10, 10 | "character": 12 11 | }, 12 | { 13 | "line": 10, 14 | "character": 81 15 | } 16 | ] 17 | }, 18 | "containerName": "Animal" 19 | }, 20 | { 21 | "name": "obj.Name", 22 | "kind": "Variable", 23 | "location": { 24 | "uri": "./samples/Animal.m", 25 | "range": [ 26 | { 27 | "line": 11, 28 | "character": 12 29 | }, 30 | { 31 | "line": 11, 32 | "character": 28 33 | } 34 | ] 35 | }, 36 | "containerName": "Animal" 37 | }, 38 | { 39 | "name": "obj.Order", 40 | "kind": "Variable", 41 | "location": { 42 | "uri": "./samples/Animal.m", 43 | "range": [ 44 | { 45 | "line": 12, 46 | "character": 12 47 | }, 48 | { 49 | "line": 12, 50 | "character": 30 51 | } 52 | ] 53 | }, 54 | "containerName": "Animal" 55 | }, 56 | { 57 | "name": "obj.Age", 58 | "kind": "Variable", 59 | "location": { 60 | "uri": "./samples/Animal.m", 61 | "range": [ 62 | { 63 | "line": 13, 64 | "character": 12 65 | }, 66 | { 67 | "line": 13, 68 | "character": 26 69 | } 70 | ] 71 | }, 72 | "containerName": "Animal" 73 | }, 74 | { 75 | "name": "obj.Tameable", 76 | "kind": "Variable", 77 | "location": { 78 | "uri": "./samples/Animal.m", 79 | "range": [ 80 | { 81 | "line": 14, 82 | "character": 12 83 | }, 84 | { 85 | "line": 15, 86 | "character": 11 87 | } 88 | ] 89 | }, 90 | "containerName": "Animal" 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /src/util/service.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | 5 | import * as crypto from './crypto'; 6 | 7 | const encoder = new TextEncoder(); 8 | 9 | export interface ServiceInterface { 10 | fetch(document: vscode.TextDocument): Promise; 11 | parse(document: vscode.TextDocument): Promise; 12 | } 13 | 14 | export abstract class ServiceBase { 15 | private _cache: Record> = {}; 16 | private _integrity: Record = {}; 17 | 18 | public async fetch(document: vscode.TextDocument | string): Promise { 19 | const filepath = typeof document === 'string' ? document : document.uri.path; 20 | const hash = await hashify(document); 21 | 22 | if ( 23 | typeof hash === 'string' && 24 | typeof this._integrity[filepath] === 'string' && 25 | hash === this._integrity[filepath] 26 | ) { 27 | return this._cache[filepath]; 28 | } 29 | 30 | if (typeof this._integrity[filepath] !== 'undefined') { 31 | delete this._integrity[filepath]; 32 | } 33 | if (typeof this._cache[filepath] !== 'undefined') { 34 | delete this._cache[filepath]; 35 | } 36 | 37 | const output = this._cache[filepath] = this.parse(document); 38 | this._integrity[filepath] = hash; 39 | return output; 40 | } 41 | 42 | public abstract parse(document: vscode.TextDocument | string): Promise; 43 | } 44 | 45 | async function hashify(document: vscode.TextDocument | string): Promise { 46 | const text = typeof document === 'string' ? document : document.getText(); 47 | if (crypto.node) { 48 | const hash = crypto.node.createHash('sha256'); 49 | hash.update(text); 50 | return hash.digest('hex'); 51 | } 52 | if (crypto.web) { 53 | const buffer = encoder.encode(text); 54 | const digest = await crypto.web.subtle.digest('SHA-256', buffer); 55 | return buf2hex(digest); 56 | } 57 | } 58 | 59 | function buf2hex(buffer: ArrayBuffer): string { 60 | return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join(''); 61 | } 62 | -------------------------------------------------------------------------------- /test/suite/providers/folding.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { documentServicePromise, foldingRangeProviderPromise } from '../../util/factory'; 6 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 7 | import { runSamplePass } from '../../util/bench'; 8 | 9 | suite('test/suite/folding.test.ts - TextmateFoldingRangeProvider class (src/folding.ts)', function() { 10 | this.timeout(10000); 11 | 12 | test('TextmateFoldingRangeProvider.provideFoldingRanges(): Promise', async function() { 13 | void vscode.window.showInformationMessage('TextmateFoldingRangeProvider class (src/folding.ts)'); 14 | const { results, samples } = await foldingRangeProviderResult(); 15 | 16 | let error: TypeError | void = void 0; 17 | for (let index = 0; index < samples.length; index++) { 18 | const basename = BASENAMES[globalThis.languageId][index]; 19 | const folds = results[index]; 20 | 21 | try { 22 | await runSamplePass('folding', basename, folds); 23 | } catch (e) { 24 | error = typeof error !== 'undefined' ? error : e as Error; 25 | } 26 | } 27 | if (error) { 28 | throw error; 29 | } 30 | }); 31 | 32 | this.afterAll(async function() { 33 | await vscode.commands.executeCommand('workbench.action.closeAllEditors'); 34 | }); 35 | }); 36 | 37 | async function foldingRangeProviderResult() { 38 | const documentService = await documentServicePromise; 39 | const foldingRangeProvider = await foldingRangeProviderPromise; 40 | 41 | const foldingContext = {}; 42 | const cancelToken = new vscode.CancellationTokenSource().token; 43 | 44 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 45 | const results: vscode.FoldingRange[][] = []; 46 | 47 | for (const resource of samples) { 48 | const document = await documentService.getDocument(resource); 49 | 50 | const folds = await foldingRangeProvider.provideFoldingRanges(document, foldingContext, cancelToken); 51 | 52 | results.push(folds); 53 | } 54 | 55 | return { results, samples }; 56 | } 57 | -------------------------------------------------------------------------------- /test/util/jsonify.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | import * as path from 'path'; 5 | import type { JsonValue, PartialDeep } from 'type-fest'; 6 | 7 | import { getTestModeExtension, TextmateScopeSelector, TextmateScopeSelectorMap } from './common'; 8 | 9 | type PartialJsonValue = PartialDeep; 10 | 11 | export function jsonify(value: Record): T { 12 | return JSON.parse(JSON.stringify(value, replaceClassesWithStrings)) as T; 13 | } 14 | 15 | function replaceClassesWithStrings(key: string, value: any): any { 16 | if (value === null || value === undefined) { 17 | return value; 18 | } 19 | 20 | // Internal {@link vscode.Uri} class has the constructor type Object. 21 | // Sometimes numerous fields are missing also. 22 | if (['', 'uri'].includes(key) && !!value && typeof value === 'object' && 'path' in value) { 23 | const externalPath = getNormalizedPathFor(value as vscode.Uri); 24 | const extensionDevelopmentUri = getTestModeExtension().extensionUri; 25 | const extensionPath = getNormalizedPathFor(extensionDevelopmentUri); 26 | return './' + path.posix.relative(extensionPath, externalPath); 27 | } 28 | 29 | if (value instanceof TextmateScopeSelector) { 30 | return value.toString(); 31 | } 32 | 33 | if (value instanceof TextmateScopeSelectorMap) { 34 | return value.toString(); 35 | } 36 | 37 | if (value instanceof RegExp) { 38 | return value.toString(); 39 | } 40 | 41 | return value; 42 | } 43 | 44 | /** 45 | * Corrects inconsistent drive letters in {@link @vscode.Uri} factories. 46 | * @param {vscode.Uri} file URI object with a `path` property. 47 | * @returns {string} Normalized path property. 48 | */ 49 | function getNormalizedPathFor(file: vscode.Uri) { 50 | const filepath = path.posix.normalize(file.path); 51 | 52 | const driveLetterIndex = filepath.indexOf('/', 1); 53 | 54 | if (driveLetterIndex >= 5) { 55 | return filepath; 56 | } 57 | 58 | const driveLetter = filepath.substring(0, driveLetterIndex).toLowerCase(); 59 | const fileSystemPath = filepath.substring(driveLetterIndex); 60 | 61 | return `${driveLetter}${fileSystemPath}`; 62 | } 63 | -------------------------------------------------------------------------------- /src/config/selectors.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { TextmateScopeSelector, TextmateScopeSelectorMap } from '../util/selectors'; 4 | 5 | import type { ConfigJson } from '.'; 6 | 7 | export class ConfigSelectors { 8 | private _assignment: { 9 | multiple: TextmateScopeSelector; 10 | separator: TextmateScopeSelector; 11 | single: TextmateScopeSelector; 12 | }; 13 | private _data: ConfigJson; 14 | private _declarations: TextmateScopeSelector; 15 | private _dedentation: TextmateScopeSelector; 16 | private _indentation: TextmateScopeSelectorMap; 17 | private _punctuation: { continuation: TextmateScopeSelector }; 18 | private _markers: { end: RegExp; start: RegExp }; 19 | private _symbols: TextmateScopeSelectorMap; 20 | 21 | constructor(data: ConfigJson) { 22 | this._data = Object.assign({}, data); 23 | } 24 | 25 | public get assignment() { 26 | return (this._assignment = this._assignment || { 27 | multiple: new TextmateScopeSelector(this._data.assignment?.multiple), 28 | separator: new TextmateScopeSelector(this._data.assignment?.separator), 29 | single: new TextmateScopeSelector(this._data.assignment?.single) 30 | }); 31 | } 32 | public get declarations() { 33 | return (this._declarations = this._declarations || new TextmateScopeSelector(this._data.declarations)); 34 | } 35 | public get dedentation() { 36 | return (this._dedentation = this._dedentation || new TextmateScopeSelector(this._data.dedentation)); 37 | } 38 | public get indentation() { 39 | return (this._indentation = this._indentation || new TextmateScopeSelectorMap(this._data.indentation)); 40 | } 41 | public get punctuation() { 42 | return (this._punctuation = this._punctuation || { 43 | continuation: new TextmateScopeSelector(this._data.punctuation?.continuation) 44 | }); 45 | } 46 | public get markers() { 47 | const start = this._data.markers?.start; 48 | const end = this._data.markers?.end; 49 | return (this._markers = this._markers || { 50 | end: end ? new RegExp(end) : /.^/, 51 | start: start ? new RegExp(start) : /.^/ 52 | }); 53 | } 54 | public get symbols() { 55 | return (this._symbols = this._symbols || new TextmateScopeSelectorMap(this._data.symbols)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/definition.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import type { ConfigData } from './config'; 5 | import type { OutlineService } from './services/outline'; 6 | 7 | export class TextmateDefinitionProvider implements vscode.DefinitionProvider { 8 | constructor(private _config: ConfigData, private _outlineService: OutlineService) {} 9 | 10 | public async provideDefinition(document: vscode.TextDocument, position: vscode.Position): Promise { 11 | const locations: vscode.Location[] = []; 12 | const filePosition = await this.getNestedPosition(document, position); 13 | if (filePosition) { 14 | locations.push(new vscode.Location(document.uri, filePosition)); 15 | } 16 | 17 | const componentGlob = this.getComponentGlob(document, position); 18 | if (!componentGlob) { 19 | return locations; 20 | } 21 | 22 | const workspaceUris = componentGlob ? await this.searchFiles(componentGlob) : []; 23 | locations.push(...workspaceUris.map(fromUriToLocation)); 24 | 25 | return locations; 26 | } 27 | 28 | private async getNestedPosition(document: vscode.TextDocument, position: vscode.Position): Promise { 29 | const range = document.getWordRangeAtPosition(position); 30 | const selection = document.getText(range); 31 | 32 | const entry = await this._outlineService.lookup(document, selection); 33 | return !entry ? void 0 : entry.location.range.start; 34 | } 35 | 36 | private getComponentGlob(document: vscode.TextDocument, position: vscode.Position): string | void { 37 | const extensions = this._config.extensions; 38 | if (!extensions) { 39 | return void 0; 40 | } 41 | 42 | const selection = document.getWordRangeAtPosition(position); 43 | const componentName = document.getText(selection); 44 | const extensionGlob = extensions.substring(1); 45 | return `**/${componentName}${extensionGlob}`; 46 | } 47 | 48 | private async searchFiles(extensionGlob: string): Promise { 49 | return vscode.workspace.findFiles(extensionGlob, this._config.exclude, 5); 50 | } 51 | } 52 | 53 | function fromUriToLocation(uri: vscode.Uri): vscode.Location { 54 | return new vscode.Location(uri, new vscode.Position(0, 0)); 55 | } 56 | -------------------------------------------------------------------------------- /scripts/pegjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // @ts-check 4 | const peggy = require('peggy'); 5 | const prettierEslint = require('prettier-eslint'); 6 | const fs = require('fs'); 7 | const shelljs = require('shelljs'); 8 | 9 | const filePath = './src/scopes/parser.ts'; 10 | let parserText = fs.readFileSync('./src/scopes/parser.pegjs', 'utf8'); 11 | 12 | const pegconfig = { 13 | 'plugins': [require('ts-pegjs')], 14 | output: 'source', 15 | cache: false, 16 | format: 'commonjs', 17 | }; 18 | 19 | const pegconfigJson = require('../src/scopes/pegconfig.json'); 20 | Object.assign(pegconfig, pegconfigJson); 21 | pegconfig.skipTypeComputation = true; 22 | pegconfig.customHeader = ''; 23 | pegconfig.tspegjs = {}; 24 | pegconfig.tspegjs.customHeader = fs.readFileSync('./src/scopes/header.ts'); 25 | 26 | parserText = peggy.generate(parserText, pegconfig); 27 | 28 | parserText = parserText.replace(/\/\/ @ts-ignore\n/g, ''); 29 | parserText = parserText.replace(/^import \b/m, 'import * as '); 30 | parserText = parserText.replace(/^type \b/gm, 'export type '); 31 | 32 | parserText = parserText.replace(/(?<=function peg\$computeLocation\([^\)]+)\)/, '?)'); 33 | parserText = parserText.replace(/(?<=function peg\$SyntaxError\([^)]+\))/, ': void'); 34 | 35 | parserText = parserText.replace('// Generated by Peggy 3.0.2.', ''); 36 | parserText = parserText.replace(/\/\/$/m, ''); 37 | parserText = parserText.replace('// https://peggyjs.org/', ''); 38 | parserText = parserText.replace('/* eslint-disable */', ''); 39 | 40 | parserText = prettierEslint({ 41 | eslintConfig: require('../.eslintrc.json'), 42 | text: parserText, 43 | logLevel: 'error', 44 | prettierOptions: require('../.prettierrc.json') 45 | }); 46 | 47 | fs.writeFileSync(filePath, parserText); 48 | 49 | // For some reason, Prettier-ESLint isn't `--fix`ing keywords & whitespace. 50 | // Let's brute-force another ESLint run just to get it fixed. 51 | shelljs.exec('npx eslint ./src/scopes/parser.ts --format ./scripts/silent-formatter.js --fix'); 52 | 53 | // Disable ESLint yet again because the file still has ~30 lint errors. 54 | parserText = fs.readFileSync('./src/scopes/parser.ts', 'utf8'); 55 | parserText = '/* eslint-disable */\n' + parserText; 56 | fs.writeFileSync(filePath, parserText); 57 | -------------------------------------------------------------------------------- /test/data/definition/Cat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "startIndex": 16, 4 | "endIndex": 22, 5 | "scopes": [ 6 | "source.matlab", 7 | "meta.class.matlab", 8 | "meta.methods.matlab", 9 | "meta.function.matlab", 10 | "meta.method-call.parens.matlab", 11 | "entity.name.type.class.matlab" 12 | ], 13 | "type": "entity.name.type.class.matlab", 14 | "text": "Animal", 15 | "line": 3, 16 | "level": 3, 17 | "uri": "./samples/Cat.m", 18 | "definition": { 19 | "uri": "./samples/Animal.m", 20 | "range": [ 21 | { 22 | "line": 0, 23 | "character": 0 24 | }, 25 | { 26 | "line": 0, 27 | "character": 0 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "startIndex": 17, 34 | "endIndex": 23, 35 | "scopes": [ 36 | "source.matlab", 37 | "meta.class.matlab", 38 | "meta.methods.matlab", 39 | "meta.function.matlab", 40 | "meta.method-call.parens.matlab", 41 | "entity.name.type.class.matlab" 42 | ], 43 | "type": "entity.name.type.class.matlab", 44 | "text": "Animal", 45 | "line": 10, 46 | "level": 3, 47 | "uri": "./samples/Cat.m", 48 | "definition": { 49 | "uri": "./samples/Animal.m", 50 | "range": [ 51 | { 52 | "line": 0, 53 | "character": 0 54 | }, 55 | { 56 | "line": 0, 57 | "character": 0 58 | } 59 | ] 60 | } 61 | }, 62 | { 63 | "startIndex": 18, 64 | "endIndex": 24, 65 | "scopes": [ 66 | "source.matlab", 67 | "meta.class.matlab", 68 | "meta.methods.matlab", 69 | "meta.function.matlab", 70 | "meta.method-call.parens.matlab", 71 | "entity.name.type.class.matlab" 72 | ], 73 | "type": "entity.name.type.class.matlab", 74 | "text": "Animal", 75 | "line": 14, 76 | "level": 3, 77 | "uri": "./samples/Cat.m", 78 | "definition": { 79 | "uri": "./samples/Animal.m", 80 | "range": [ 81 | { 82 | "line": 0, 83 | "character": 0 84 | }, 85 | { 86 | "line": 0, 87 | "character": 0 88 | } 89 | ] 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /test/data/definition/Dog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "startIndex": 16, 4 | "endIndex": 22, 5 | "scopes": [ 6 | "source.matlab", 7 | "meta.class.matlab", 8 | "meta.methods.matlab", 9 | "meta.function.matlab", 10 | "meta.method-call.parens.matlab", 11 | "entity.name.type.class.matlab" 12 | ], 13 | "type": "entity.name.type.class.matlab", 14 | "text": "Animal", 15 | "line": 3, 16 | "level": 3, 17 | "uri": "./samples/Dog.m", 18 | "definition": { 19 | "uri": "./samples/Animal.m", 20 | "range": [ 21 | { 22 | "line": 0, 23 | "character": 0 24 | }, 25 | { 26 | "line": 0, 27 | "character": 0 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "startIndex": 17, 34 | "endIndex": 23, 35 | "scopes": [ 36 | "source.matlab", 37 | "meta.class.matlab", 38 | "meta.methods.matlab", 39 | "meta.function.matlab", 40 | "meta.method-call.parens.matlab", 41 | "entity.name.type.class.matlab" 42 | ], 43 | "type": "entity.name.type.class.matlab", 44 | "text": "Animal", 45 | "line": 10, 46 | "level": 3, 47 | "uri": "./samples/Dog.m", 48 | "definition": { 49 | "uri": "./samples/Animal.m", 50 | "range": [ 51 | { 52 | "line": 0, 53 | "character": 0 54 | }, 55 | { 56 | "line": 0, 57 | "character": 0 58 | } 59 | ] 60 | } 61 | }, 62 | { 63 | "startIndex": 18, 64 | "endIndex": 24, 65 | "scopes": [ 66 | "source.matlab", 67 | "meta.class.matlab", 68 | "meta.methods.matlab", 69 | "meta.function.matlab", 70 | "meta.method-call.parens.matlab", 71 | "entity.name.type.class.matlab" 72 | ], 73 | "type": "entity.name.type.class.matlab", 74 | "text": "Animal", 75 | "line": 14, 76 | "level": 3, 77 | "uri": "./samples/Dog.m", 78 | "definition": { 79 | "uri": "./samples/Animal.m", 80 | "range": [ 81 | { 82 | "line": 0, 83 | "character": 0 84 | }, 85 | { 86 | "line": 0, 87 | "character": 0 88 | } 89 | ] 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /test/data/definition/Horse.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "startIndex": 16, 4 | "endIndex": 22, 5 | "scopes": [ 6 | "source.matlab", 7 | "meta.class.matlab", 8 | "meta.methods.matlab", 9 | "meta.function.matlab", 10 | "meta.method-call.parens.matlab", 11 | "entity.name.type.class.matlab" 12 | ], 13 | "type": "entity.name.type.class.matlab", 14 | "text": "Animal", 15 | "line": 3, 16 | "level": 3, 17 | "uri": "./samples/Horse.m", 18 | "definition": { 19 | "uri": "./samples/Animal.m", 20 | "range": [ 21 | { 22 | "line": 0, 23 | "character": 0 24 | }, 25 | { 26 | "line": 0, 27 | "character": 0 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "startIndex": 17, 34 | "endIndex": 23, 35 | "scopes": [ 36 | "source.matlab", 37 | "meta.class.matlab", 38 | "meta.methods.matlab", 39 | "meta.function.matlab", 40 | "meta.method-call.parens.matlab", 41 | "entity.name.type.class.matlab" 42 | ], 43 | "type": "entity.name.type.class.matlab", 44 | "text": "Animal", 45 | "line": 16, 46 | "level": 3, 47 | "uri": "./samples/Horse.m", 48 | "definition": { 49 | "uri": "./samples/Animal.m", 50 | "range": [ 51 | { 52 | "line": 0, 53 | "character": 0 54 | }, 55 | { 56 | "line": 0, 57 | "character": 0 58 | } 59 | ] 60 | } 61 | }, 62 | { 63 | "startIndex": 18, 64 | "endIndex": 24, 65 | "scopes": [ 66 | "source.matlab", 67 | "meta.class.matlab", 68 | "meta.methods.matlab", 69 | "meta.function.matlab", 70 | "meta.method-call.parens.matlab", 71 | "entity.name.type.class.matlab" 72 | ], 73 | "type": "entity.name.type.class.matlab", 74 | "text": "Animal", 75 | "line": 20, 76 | "level": 3, 77 | "uri": "./samples/Horse.m", 78 | "definition": { 79 | "uri": "./samples/Animal.m", 80 | "range": [ 81 | { 82 | "line": 0, 83 | "character": 0 84 | }, 85 | { 86 | "line": 0, 87 | "character": 0 88 | } 89 | ] 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /test/data/definition/Snake.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "startIndex": 16, 4 | "endIndex": 22, 5 | "scopes": [ 6 | "source.matlab", 7 | "meta.class.matlab", 8 | "meta.methods.matlab", 9 | "meta.function.matlab", 10 | "meta.method-call.parens.matlab", 11 | "entity.name.type.class.matlab" 12 | ], 13 | "type": "entity.name.type.class.matlab", 14 | "text": "Animal", 15 | "line": 3, 16 | "level": 3, 17 | "uri": "./samples/Snake.m", 18 | "definition": { 19 | "uri": "./samples/Animal.m", 20 | "range": [ 21 | { 22 | "line": 0, 23 | "character": 0 24 | }, 25 | { 26 | "line": 0, 27 | "character": 0 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "startIndex": 17, 34 | "endIndex": 23, 35 | "scopes": [ 36 | "source.matlab", 37 | "meta.class.matlab", 38 | "meta.methods.matlab", 39 | "meta.function.matlab", 40 | "meta.method-call.parens.matlab", 41 | "entity.name.type.class.matlab" 42 | ], 43 | "type": "entity.name.type.class.matlab", 44 | "text": "Animal", 45 | "line": 14, 46 | "level": 3, 47 | "uri": "./samples/Snake.m", 48 | "definition": { 49 | "uri": "./samples/Animal.m", 50 | "range": [ 51 | { 52 | "line": 0, 53 | "character": 0 54 | }, 55 | { 56 | "line": 0, 57 | "character": 0 58 | } 59 | ] 60 | } 61 | }, 62 | { 63 | "startIndex": 18, 64 | "endIndex": 24, 65 | "scopes": [ 66 | "source.matlab", 67 | "meta.class.matlab", 68 | "meta.methods.matlab", 69 | "meta.function.matlab", 70 | "meta.method-call.parens.matlab", 71 | "entity.name.type.class.matlab" 72 | ], 73 | "type": "entity.name.type.class.matlab", 74 | "text": "Animal", 75 | "line": 18, 76 | "level": 3, 77 | "uri": "./samples/Snake.m", 78 | "definition": { 79 | "uri": "./samples/Animal.m", 80 | "range": [ 81 | { 82 | "line": 0, 83 | "character": 0 84 | }, 85 | { 86 | "line": 0, 87 | "character": 0 88 | } 89 | ] 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /test/util/bench.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 'use strict'; 3 | 4 | import { deepEqual } from './assert'; 5 | import { isWebRuntime } from './runtime'; 6 | import { loadJsonFile, writeJsonFile } from './common'; 7 | import { getComponentSampleDataUri } from './files'; 8 | import { jsonify } from './jsonify'; 9 | 10 | import type { PartialDeep, JsonObject } from 'type-fest'; 11 | 12 | type PartialJsonObject = PartialDeep; 13 | 14 | /** 15 | * Generate test pass for a VS Code language provider's output vs a sample MATLAB class. 16 | * @param {string} component Name of callee test object component - `src\*.js`. 17 | * @param {string} sample Basename of target sample file - `data\*\*\*.json`. 18 | * @param {object[]} output Result array generated by component test. 19 | */ 20 | export async function runSamplePass(component: string, basename: string, output: Array>) { 21 | const data = getComponentSampleDataUri(component, basename); 22 | const filepath = `./test/data/${component}/${basename}.json`; 23 | let expectedJson: PartialJsonObject | void = void 0; 24 | 25 | // We used to check if file exists using `vscode.FileSystem.stat`. 26 | // But because we override data anyways, just fetch snapshot JSON. 27 | try { 28 | expectedJson = await loadJsonFile(data); 29 | } catch (_) { 30 | /* swallow exception and delegate to assert */ 31 | } 32 | 33 | const outputJson = jsonify(output); 34 | 35 | // Run JSON diff assert and record error. 36 | let error: TypeError | void = void 0; 37 | try { 38 | deepEqual(outputJson, expectedJson || [], filepath); 39 | } catch (e) { 40 | error = e as Error; 41 | } 42 | 43 | // In Node runtime, dump output to data component subdirectory. 44 | if (!isWebRuntime && error) { 45 | await writeJsonFile(data, outputJson); 46 | } 47 | 48 | // In web runtime, dump output to terminal console (web runtime). 49 | if (isWebRuntime && error) { 50 | console.log(`${filepath} expected contents:`); 51 | console.log(''); 52 | console.log(` ${JSON.stringify(expectedJson)}`); 53 | console.log(''); 54 | console.log(`${filepath} actual contents:`); 55 | console.log(''); 56 | console.log(` ${JSON.stringify(outputJson)}`); 57 | console.log(''); 58 | } 59 | 60 | // Throw an uncaught exception to exit Mocha test closure. 61 | if (error) { 62 | throw error; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/document-symbol.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import type { OutlineService, OutlineEntry } from './services/outline'; 5 | 6 | interface LanguageSymbol { 7 | readonly children: vscode.DocumentSymbol[]; 8 | readonly level: number; 9 | readonly parent: LanguageSymbol | undefined; 10 | } 11 | 12 | export class TextmateDocumentSymbolProvider implements vscode.DocumentSymbolProvider { 13 | constructor(private _outlineService: OutlineService) {} 14 | 15 | public async provideDocumentSymbolInformation(document: vscode.TextDocument): Promise { 16 | const outline = await this._outlineService.fetch(document); 17 | return outline.map(this.toSymbolInformation.bind(outline) as typeof this.toSymbolInformation, outline); 18 | } 19 | 20 | public async provideDocumentSymbols(document: vscode.TextDocument): Promise { 21 | const outline = await this._outlineService.fetch(document); 22 | const root: LanguageSymbol = { 23 | children: [], 24 | level: -Infinity, 25 | parent: undefined 26 | }; 27 | if (outline.length !== 0) { 28 | this.traverseAndCopy(root, outline); 29 | } 30 | return root.children; 31 | } 32 | 33 | private traverseAndCopy(parent: LanguageSymbol, entries: OutlineEntry[]) { 34 | const entry = entries[0]; 35 | const symbol = this.toDocumentSymbol(entry); 36 | symbol.children = []; 37 | 38 | while (parent && entry.level <= parent.level) { 39 | parent = parent.parent!; 40 | } 41 | parent.children.push(symbol); 42 | if (entries.length > 1) { 43 | this.traverseAndCopy( 44 | { 45 | children: symbol.children, 46 | level: entry.level, 47 | parent 48 | }, 49 | entries.slice(1) 50 | ); 51 | } 52 | } 53 | 54 | private toSymbolInformation(this: OutlineEntry[], entry: OutlineEntry, index: number): vscode.SymbolInformation { 55 | let container: string | void; 56 | for (let subindex = index - 1; subindex > -1; subindex--) { 57 | const subentry = this[subindex]; 58 | if (subentry.level < entry.level) { 59 | container = subentry.text; 60 | break; 61 | } 62 | } 63 | return new vscode.SymbolInformation(entry.text, entry.type, container || '', entry.location); 64 | } 65 | 66 | private toDocumentSymbol(entry: OutlineEntry) { 67 | return new vscode.DocumentSymbol( 68 | entry.text, 69 | entry.token 70 | .replace(/^meta\./, '') 71 | .replace(/\.[^.]$/, ''), 72 | entry.type, 73 | entry.location.range, 74 | entry.location.range 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import type * as vscode from 'vscode'; 4 | import { ConfigSelectors } from './selectors'; 5 | 6 | import type { PartialDeep, JsonObject } from 'type-fest'; 7 | import type { LanguageDefinition } from '../util/contributes'; 8 | 9 | export type SelectorSource = string[] | string; 10 | 11 | type PartialJsonObject = PartialDeep; 12 | 13 | export interface ConfigJson extends PartialJsonObject { 14 | assignment?: { 15 | multiple?: SelectorSource; 16 | separator?: SelectorSource; 17 | single?: SelectorSource; 18 | }; 19 | declarations?: SelectorSource; 20 | dedentation?: SelectorSource; 21 | exclude?: string; 22 | indentation?: { 23 | [selector: string]: 1 | -1 | undefined; 24 | }; 25 | punctuation?: { 26 | continuation?: SelectorSource; 27 | }; 28 | markers?: { 29 | end?: string; 30 | start?: string; 31 | }; 32 | symbols?: { 33 | [selector: string]: vscode.SymbolKind | undefined; 34 | }; 35 | } 36 | 37 | export class ConfigData { 38 | public readonly language: LanguageDefinition; 39 | public readonly extensions?: string; 40 | public readonly exclude?: string; 41 | public readonly selectors: ConfigSelectors; 42 | public readonly include: string; 43 | 44 | constructor(json: ConfigJson, language: LanguageDefinition) { 45 | this.include = generateIncludePattern(language); 46 | if (language.extensions) { 47 | this.extensions = generateExtensionPattern(language.extensions); 48 | } 49 | if (json.exclude) { 50 | this.exclude = json.exclude; 51 | } 52 | this.selectors = new ConfigSelectors(json); 53 | } 54 | } 55 | 56 | function generateExtensionPattern(extensions: string[] | undefined): string { 57 | if (extensions.length === 1) { 58 | return `*${extensions[0]}`; 59 | } else { 60 | const segments = extensions.map(e => e.substring(1)); 61 | return `*.{${segments.join(',')}}`; 62 | } 63 | } 64 | 65 | function generateIncludePattern(language: LanguageDefinition): string { 66 | if (!language.extensions && !language.filenames) { 67 | return '**/*'; 68 | } 69 | let extensions: string; 70 | let filenames: string; 71 | if (language.extensions?.length) { 72 | extensions = generateExtensionPattern(language.extensions); 73 | } 74 | if (language.filenames?.length) { 75 | filenames = language.filenames.length === 1 ? language.filenames[0] : `{${language.filenames.join(',')}}`; 76 | } 77 | if (extensions && filenames) { 78 | return `**/{${extensions},${filenames}}`; 79 | } 80 | if (extensions) { 81 | return `**/${extensions}`; 82 | } 83 | if (filenames) { 84 | return `**/${filenames}`; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/util/factory.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import TextmateLanguageService from '../../src/main'; 4 | 5 | // Factory function for language service. 6 | function constructLanguageService(id: string): TextmateLanguageService { 7 | return new TextmateLanguageService(id, globalThis.extensionContext); 8 | } 9 | 10 | // Factory function for language service component. 11 | function initServiceComponent(ls: TextmateLanguageService, name: string): T { 12 | return ls[name]() as T; 13 | } 14 | 15 | type DocumentService = ReturnType; 16 | type TokenService = ReturnType; 17 | type TextmateOutlineService = ReturnType; 18 | type TextmateFoldingRangeProvider = ReturnType; 19 | type TextmateDefinitionProvider = ReturnType; 20 | type TextmateDocumentSymbolProvider = ReturnType; 21 | type TextmateWorkspaceSymbolProvider = ReturnType; 22 | 23 | /** `TextmateLanguageService` factory. */ 24 | export const textmateService = constructLanguageService(globalThis.languageId); 25 | 26 | /** `DocumentService` component. */ 27 | export const documentServicePromise = initServiceComponent(textmateService, 'initDocumentService'); 28 | 29 | /** `TokenizerService` component. */ 30 | export const tokenServicePromise = initServiceComponent(textmateService, 'initTokenService'); 31 | 32 | /** `OutlineService` component. */ 33 | export const outlineServicePromise = initServiceComponent(textmateService, 'initOutlineService'); 34 | 35 | /** `TextmateFoldingRangeProvider` component. */ 36 | export const foldingRangeProviderPromise = initServiceComponent(textmateService, 'createFoldingRangeProvider'); 37 | 38 | /** `TextmateDefinitionProvider` component. */ 39 | export const definitionProviderPromise = initServiceComponent(textmateService, 'createDefinitionProvider'); 40 | 41 | /** `TextmateDocumentSymbolProvider` component. */ 42 | export const documentSymbolProviderPromise = initServiceComponent(textmateService, 'createDocumentSymbolProvider'); 43 | 44 | 45 | /** `TextmateWorkspaceSymbolProvider` component. */ 46 | export const workspaceSymbolProviderPromise = initServiceComponent(textmateService, 'createWorkspaceSymbolProvider'); 47 | 48 | /** API methods for grammar and token scope resolution. */ 49 | export const api = TextmateLanguageService.api; 50 | -------------------------------------------------------------------------------- /test/suite/services/document.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { strictEqual } from '../../util/assert'; 5 | 6 | import { documentServicePromise } from '../../util/factory'; 7 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 8 | import { jsonify } from '../../util/jsonify'; 9 | 10 | import type { FullTextDocument } from '../../../src/services/document'; 11 | 12 | suite('test/suite/document.test.ts - DocumentService class (src/services/document.ts)', function() { 13 | this.timeout(5000); 14 | 15 | test('FullTextDocument.uri', async function() { 16 | void vscode.window.showInformationMessage('DocumentService class (src/services/document.ts)'); 17 | const { actuals, expecteds, filenames, samples } = await documentServiceOutput(); 18 | 19 | for (let index = 0; index < samples.length; index++) { 20 | const textDocument = expecteds[index]; 21 | const providerDocument = actuals[index]; 22 | strictEqual(textDocument.uri.toString(true), providerDocument.uri.toString(true), filenames[index]); 23 | } 24 | }); 25 | 26 | test('FullTextDocument.lineCount', async function() { 27 | const { actuals, expecteds, filenames, samples } = await documentServiceOutput(); 28 | 29 | for (let index = 0; index < samples.length; index++) { 30 | const textDocument = expecteds[index]; 31 | const providerDocument = actuals[index]; 32 | strictEqual(textDocument.lineCount, providerDocument.lineCount, filenames[index]); 33 | } 34 | }); 35 | 36 | test('FullTextDocument.lineAt(line: number)', async function() { 37 | const { actuals, expecteds, filenames, samples } = await documentServiceOutput(); 38 | 39 | for (let index = 0; index < samples.length; index++) { 40 | const textDocument = expecteds[index]; 41 | const providerDocument = actuals[index]; 42 | strictEqual(textDocument.lineAt(0).text, providerDocument.lineAt(0).text, filenames[index]); 43 | } 44 | }); 45 | 46 | this.afterAll(async function() { 47 | await vscode.commands.executeCommand('workbench.action.closeAllEditors'); 48 | }); 49 | }); 50 | 51 | async function documentServiceOutput() { 52 | const documentService = await documentServicePromise; 53 | 54 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 55 | 56 | const expecteds: vscode.TextDocument[] = []; 57 | const actuals: vscode.TextDocument[] = []; 58 | const filenames: string[] = []; 59 | 60 | for (const resource of samples) { 61 | const textDocument = await vscode.workspace.openTextDocument(resource); 62 | const providerDocument = await documentService.getDocument(resource); 63 | 64 | expecteds.push(textDocument); 65 | actuals.push(providerDocument); 66 | 67 | filenames.push(jsonify(textDocument.uri)); 68 | } 69 | 70 | return { actuals, expecteds, filenames, samples }; 71 | } 72 | -------------------------------------------------------------------------------- /src/workspace-symbol.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/unbound-method */ 2 | 'use strict'; 3 | 4 | import type * as vscode from 'vscode'; 5 | import { Disposable } from './util/dispose'; 6 | import { lazy } from './util/lazy'; 7 | import type { Lazy } from './util/lazy'; 8 | import type { TextmateDocumentSymbolProvider } from './document-symbol'; 9 | import type { DocumentService } from './services/document'; 10 | 11 | export class TextmateWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider { 12 | private _symbolCache = new Map>>(); 13 | private _symbolCachePopulated = false; 14 | 15 | constructor(private _documentService: DocumentService, private _documentSymbols: TextmateDocumentSymbolProvider) { 16 | super(); 17 | } 18 | 19 | public async provideWorkspaceSymbols(query: string): Promise { 20 | if (!this._symbolCachePopulated) { 21 | await this.populateSymbolCache(); 22 | this._symbolCachePopulated = true; 23 | this._documentService.onDidChangeDocument(this.onDidChangeDocument, this, this._disposables); 24 | this._documentService.onDidCreateDocument(this.onDidChangeDocument, this, this._disposables); 25 | this._documentService.onDidDeleteDocument(this.onDidDeleteDocument, this, this._disposables); 26 | } 27 | 28 | const allSymbolsSets = await Promise.all(Array.from(this._symbolCache.values(), x => x.value)); 29 | const allSymbols = [].concat(...allSymbolsSets); 30 | return allSymbols.filter(symbolNameMatchesQuery(query.toLowerCase()), allSymbols); 31 | } 32 | 33 | private async populateSymbolCache(): Promise { 34 | const documentUris = await this._documentService.getAllDocuments(); 35 | for (const document of documentUris) { 36 | this._symbolCache.set(document.uri.fsPath, this.getSymbols(document)); 37 | } 38 | } 39 | 40 | private getSymbols(document: vscode.TextDocument): Lazy> { 41 | const provideDocumentSymbolInformation = this._documentSymbols.provideDocumentSymbolInformation 42 | .bind(this._documentSymbols, document) as () => Promise; 43 | return lazy(provideDocumentSymbolInformation); 44 | } 45 | 46 | private onDidChangeDocument(document: vscode.TextDocument) { 47 | this._symbolCache.set(document.uri.fsPath, this.getSymbols(document)); 48 | } 49 | 50 | private onDidDeleteDocument(resource: vscode.Uri) { 51 | this._symbolCache.delete(resource.fsPath); 52 | } 53 | } 54 | 55 | function symbolNameMatchesQuery(query: string): (s: vscode.SymbolInformation) => s is vscode.SymbolInformation { 56 | return function(symbol: vscode.SymbolInformation): symbol is vscode.SymbolInformation { 57 | return symbol.name.toLowerCase().indexOf(query.toLowerCase()) !== -1; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /test/data/outline/Dog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anchor": 2, 4 | "level": 0, 5 | "line": 0, 6 | "location": { 7 | "uri": "./samples/Dog.m", 8 | "range": [ 9 | { 10 | "line": 0, 11 | "character": 9 12 | }, 13 | { 14 | "line": 18, 15 | "character": 0 16 | } 17 | ] 18 | }, 19 | "text": "Dog", 20 | "token": "entity.name.type.class.matlab", 21 | "type": 4 22 | }, 23 | { 24 | "anchor": 8, 25 | "level": 1, 26 | "line": 1, 27 | "location": { 28 | "uri": "./samples/Dog.m", 29 | "range": [ 30 | { 31 | "line": 1, 32 | "character": 4 33 | }, 34 | { 35 | "line": 18, 36 | "character": 0 37 | } 38 | ] 39 | }, 40 | "text": "methods", 41 | "token": "keyword.control.methods.matlab", 42 | "type": 2 43 | }, 44 | { 45 | "anchor": 16, 46 | "level": 2, 47 | "line": 2, 48 | "location": { 49 | "uri": "./samples/Dog.m", 50 | "range": [ 51 | { 52 | "line": 2, 53 | "character": 23 54 | }, 55 | { 56 | "line": 4, 57 | "character": 11 58 | } 59 | ] 60 | }, 61 | "text": "Dog", 62 | "token": "entity.name.function.matlab", 63 | "type": 11 64 | }, 65 | { 66 | "anchor": 55, 67 | "level": 2, 68 | "line": 5, 69 | "location": { 70 | "uri": "./samples/Dog.m", 71 | "range": [ 72 | { 73 | "line": 5, 74 | "character": 23 75 | }, 76 | { 77 | "line": 7, 78 | "character": 11 79 | } 80 | ] 81 | }, 82 | "text": "noise", 83 | "token": "entity.name.function.matlab", 84 | "type": 11 85 | }, 86 | { 87 | "anchor": 76, 88 | "level": 2, 89 | "line": 8, 90 | "location": { 91 | "uri": "./samples/Dog.m", 92 | "range": [ 93 | { 94 | "line": 8, 95 | "character": 23 96 | }, 97 | { 98 | "line": 11, 99 | "character": 11 100 | } 101 | ] 102 | }, 103 | "text": "move", 104 | "token": "entity.name.function.matlab", 105 | "type": 11 106 | }, 107 | { 108 | "anchor": 115, 109 | "level": 2, 110 | "line": 12, 111 | "location": { 112 | "uri": "./samples/Dog.m", 113 | "range": [ 114 | { 115 | "line": 12, 116 | "character": 23 117 | }, 118 | { 119 | "line": 18, 120 | "character": 0 121 | } 122 | ] 123 | }, 124 | "text": "sleep", 125 | "token": "entity.name.function.matlab", 126 | "type": 11 127 | } 128 | ] 129 | -------------------------------------------------------------------------------- /test/context/mock.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as path from 'path'; 4 | import * as vscode from 'vscode'; 5 | 6 | import { MockGlobalMemento, MockMemento } from './memento'; 7 | import { StubSecretStorage } from './secret'; 8 | import { MockEnvironmentVariableCollection } from './variables'; 9 | 10 | import { ContributorData } from '../util/common'; 11 | 12 | const contributorData = new ContributorData(); 13 | 14 | export class MockExtensionContext implements vscode.ExtensionContext { 15 | public readonly subscriptions: vscode.Disposable[]; 16 | 17 | public readonly workspaceState: vscode.Memento; 18 | public readonly globalState: vscode.Memento & { setKeysForSync(_: readonly string[]): void }; 19 | 20 | public readonly secrets: vscode.SecretStorage; 21 | 22 | public readonly environmentVariableCollection: vscode.EnvironmentVariableCollection; 23 | 24 | public readonly storageUri: vscode.Uri; 25 | public readonly storagePath: string; 26 | public readonly globalStorageUri: vscode.Uri; 27 | public readonly globalStoragePath: string; 28 | public readonly logUri: vscode.Uri; 29 | public readonly logPath: string; 30 | 31 | public readonly extensionUri: vscode.Uri; 32 | public readonly extensionPath: string; 33 | 34 | public asAbsolutePath: (relativePath: string) => string; 35 | 36 | public readonly extensionMode: vscode.ExtensionMode; 37 | public readonly extension: vscode.Extension; 38 | 39 | constructor(id: string) { 40 | this.subscriptions = []; 41 | 42 | this.workspaceState = new MockMemento(); 43 | this.globalState = new MockGlobalMemento(); 44 | 45 | this.secrets = new StubSecretStorage(); 46 | 47 | this.environmentVariableCollection = new MockEnvironmentVariableCollection(); 48 | 49 | const codeRoot = path.posix.resolve(path.posix.normalize(vscode.env.appRoot), '../../..'); 50 | this.globalStoragePath = `${codeRoot}/user-data/User/globalStorage`; 51 | this.globalStorageUri = vscode.Uri.file(this.globalStoragePath); 52 | this.storagePath = `${codeRoot}/user-data/User/workspaceStorage`; 53 | this.storageUri = vscode.Uri.file(this.globalStoragePath); 54 | this.logPath = `${codeRoot}/user-data/User/logs`; 55 | this.logUri = vscode.Uri.file(this.logPath); 56 | 57 | const extension = contributorData.getExtensionFromLanguageId(id); 58 | if (typeof extension === 'undefined') { 59 | if (globalThis.languageId === id) { 60 | throw new Error('Could not find extension for language ID "' + id +'"'); 61 | } 62 | return; 63 | } 64 | 65 | const extensionUri = this.extensionUri = extension.extensionUri; 66 | this.extensionPath = extension.extensionUri.path; 67 | 68 | this.asAbsolutePath = function(relativePath: string): string { 69 | return vscode.Uri.joinPath(extensionUri, relativePath).toString(); 70 | }; 71 | 72 | this.extensionMode = vscode.ExtensionMode.Development; 73 | this.extension = extension; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 |
4 | 5 | This theme follows a bimonthly-to-monthly release schedule. Releases occur on the first day of every month and/or two weeks prior to 12:00AM UTC. This ensures that users will not be spammed with updates from NPM and Dependabot. 6 | 7 | Here are a few ways you can contribute to this repository: 8 | 9 | - Create issues 10 | - Request features 11 | - Report bugs 12 | - Open pull requests 13 | 14 | ## Create issues 15 | 16 | ### Request features 17 | 18 | If you are requesting features, please check if a similar issue exists. If the feature request does not exist, please make a feature request & include the following information: 19 | 20 | - Precise description of feature. 21 | - Is the feature probably a major or minor change? 22 | - Major = new dependencies, features or full rework. 23 | - Minor = developer changes or minor implementation-related work. 24 | - Is this item on the [roadmap] or not? 25 | 26 | Please limit your requests to one feature per issue. 27 | 28 | ### Report bugs 29 | 30 | If you are reporting bugs, please include the following information in your report: 31 | 32 | - Broswer version 33 | - OS version 34 | - Dependent extension ID 35 | - Does this issue occur when other extensions are disabled? 36 | - Steps to reproduce 37 | - Code sample or screenshot (MVCE) 38 | - Bug description (expected vs actual behaviour) 39 | 40 | Please limit your description and code sample/screenshot(s) to one MVCE (Minimum Complete Verifiable Example). 41 | 42 | ## Open pull requests 43 | 44 | 1. Fork the project 45 | 46 | 2. Clone your fork 47 | 48 | 3. Create a **`feature`** branch 49 | 50 | ```sh 51 | git checkout -b cool-feature 52 | ``` 53 | 54 | 4. Commit your changes 55 | 56 | You **must** [link your commit to an issue][github-pr-link] to have your commit accepted: 57 | ```sh 58 | git commit -m "Added cool feature (#1337)" 59 | ``` 60 | 61 | 5. Run tests targeting the [latest version of all Visual Studio Code distribution channels][vscode-download]. 62 | ```sh 63 | npm test 64 | ``` 65 | Installation links: 66 | - Latest version of [VS Code][vscode-download]. 67 | - Latest version of [VS Code Insiders][vscode-insiders-download]. 68 | - Latest version of [VSCodium][vscodium-download]. 69 | 70 | 71 | 72 | [roadmap]: https://github.com/vsce-toolroom/vscode-textmate-languageservice/blob/main/CHANGELOG.md#roadmap 73 | 74 | [github-pr-link]: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword 75 | [vscode-download]: https://code.visualstudio.com/Download 76 | [vscode-insiders-download]: https://code.visualstudio.com/insiders/ 77 | [vscodium-download]: https://vscodium.com/#install 78 | -------------------------------------------------------------------------------- /src/services/outline.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | import { TextmateScopeSelector } from '../util/selectors'; 6 | import { ServiceBase } from '../util/service'; 7 | 8 | import type { ConfigData } from '../config'; 9 | import type { TokenizerService, TextmateToken } from './tokenizer'; 10 | 11 | const entitySelector = new TextmateScopeSelector('entity'); 12 | 13 | export interface OutlineEntry { 14 | readonly level: number; 15 | readonly line: number; 16 | readonly location: vscode.Location; 17 | readonly text: string; 18 | readonly token: string; 19 | readonly type: vscode.SymbolKind; 20 | readonly anchor: number; 21 | } 22 | 23 | export class OutlineService extends ServiceBase { 24 | constructor(private _config: ConfigData, private _tokenService: TokenizerService) { 25 | super(); 26 | } 27 | 28 | public async lookup(document: vscode.TextDocument, text: string): Promise { 29 | const outline = await this.fetch(document); 30 | return outline.find(entry => entry.text === text); 31 | } 32 | 33 | public async parse(document: vscode.TextDocument): Promise { 34 | const outline: OutlineEntry[] = []; 35 | const tokens = await this._tokenService.fetch(document); 36 | 37 | for (let index = 0; index < tokens.length; index++) { 38 | const entry = tokens[index]; 39 | if (!this.isSymbolToken(entry)) { 40 | continue; 41 | } 42 | 43 | const lineNumber = entry.line; 44 | const symbolKind = this._config.selectors.symbols.value(entry.scopes) as number; 45 | 46 | if (outline.length > 0 && lineNumber === outline[outline.length - 1].line) { 47 | continue; 48 | } 49 | 50 | outline.push({ 51 | anchor: index, 52 | level: entry.level, 53 | line: lineNumber, 54 | location: new vscode.Location( 55 | document.uri, 56 | new vscode.Range(lineNumber, entry.startIndex, lineNumber, entry.endIndex) 57 | ), 58 | text: entry.text, 59 | token: entry.type, 60 | type: symbolKind 61 | }); 62 | } 63 | 64 | // Get full range of section 65 | return outline.map(function(entry: OutlineEntry, startIndex: number): OutlineEntry { 66 | let end: number; 67 | for (let i = startIndex + 1; i < outline.length; ++i) { 68 | if (outline[i].level <= entry.level) { 69 | end = outline[i].line - 1; 70 | break; 71 | } 72 | } 73 | const endLine = end ?? document.lineCount - 1; 74 | return { 75 | ...entry, 76 | location: new vscode.Location(document.uri, 77 | new vscode.Range( 78 | entry.location.range.start, 79 | new vscode.Position(endLine, document.lineAt(endLine).text.length) 80 | ) 81 | ) 82 | }; 83 | }); 84 | } 85 | 86 | private isSymbolToken(token: TextmateToken): boolean { 87 | const isEntity = entitySelector.match(token.scopes); 88 | return ( 89 | this._config.selectors.symbols.has(token.scopes) 90 | && (!isEntity || this._config.selectors.declarations.match(token.scopes)) 91 | && (!this._config.selectors.assignment.separator.match(token.scopes)) 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/extensions/vscode-mediawiki/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": [ 4 | "" 6 | ] 7 | }, 8 | "brackets": [ 9 | [ 10 | "[", 11 | "]" 12 | ], 13 | [ 14 | "-{", 15 | "}-" 16 | ], 17 | [ 18 | "{|", 19 | "|}" 20 | ], 21 | [ 22 | "{{", 23 | "}}" 24 | ], 25 | [ 26 | "{{{", 27 | "}}}" 28 | ] 29 | ], 30 | "autoClosingPairs": [ 31 | [ 32 | "{", 33 | "}" 34 | ], 35 | [ 36 | "[", 37 | "]" 38 | ], 39 | [ 40 | "-{", 41 | "}-" 42 | ], 43 | [ 44 | "" 46 | ], 47 | [ 48 | "", 49 | "" 50 | ], 51 | [ 52 | "
", 53 | "
" 54 | ], 55 | [ 56 | "", 57 | "" 58 | ], 59 | [ 60 | "", 61 | "" 62 | ], 63 | [ 64 | "", 65 | "" 66 | ], 67 | [ 68 | "
", 69 | "
" 70 | ], 71 | [ 72 | "", 73 | "" 74 | ], 75 | [ 76 | "", 77 | "" 78 | ], 79 | [ 80 | "", 81 | "" 82 | ], 83 | [ 84 | "", 85 | "" 86 | ], 87 | [ 88 | "", 89 | "" 90 | ], 91 | [ 92 | "", 93 | "" 94 | ], 95 | [ 96 | "", 97 | "" 98 | ], 99 | [ 100 | "", 101 | "" 102 | ], 103 | [ 104 | "
",
105 |           "
" 106 | ], 107 | [ 108 | "", 109 | "" 110 | ], 111 | [ 112 | "", 113 | "" 114 | ], 115 | [ 116 | "", 117 | "" 118 | ], 119 | [ 120 | "", 121 | "" 122 | ], 123 | [ 124 | "", 125 | "" 126 | ], 127 | [ 128 | "", 129 | "" 130 | ] 131 | ], 132 | "surroundingPairs": [ 133 | [ 134 | "{", 135 | "}" 136 | ], 137 | [ 138 | "[", 139 | "]" 140 | ], 141 | [ 142 | "(", 143 | ")" 144 | ], 145 | [ 146 | "'", 147 | "'" 148 | ] 149 | ], 150 | "folding": { 151 | "markers": { 152 | "start": "^\\s*", 153 | "end": "^\\s*" 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /test/util/assert.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { dequal } from 'dequal'; 4 | import * as LineDiff from 'line-diff'; 5 | import { jsonify } from './jsonify'; 6 | 7 | export function deepEqual(actual: any, expected: any, message?: string) { 8 | if (!dequal(actual, expected)) { 9 | throw new TypeError(generateMessage(actual, expected, message)); 10 | } 11 | } 12 | 13 | export function strictEqual(actual: any, expected: any, message?: string) { 14 | if (actual !== expected || isNaN(actual) !== isNaN(expected)) { 15 | throw new TypeError(generateMessage(actual, expected, message)); 16 | } 17 | } 18 | 19 | function isNaN(x: any) { 20 | return x !== x; 21 | } 22 | 23 | function generateMessage(actual: any, expected: any, message?: string): string { 24 | const appositive = message ? ` (${message})` : ''; 25 | 26 | const prettyJsonActual = typeof actual !== 'string' 27 | ? JSON.stringify(jsonify(actual), null, 2) 28 | : actual; 29 | const prettyJsonExpected = typeof expected !== 'string' 30 | ? JSON.stringify(jsonify(expected), null, 2) 31 | : expected; 32 | 33 | const jsonDiff = generateCustomLineDiff(prettyJsonActual, prettyJsonExpected); 34 | 35 | return `Expected values to be strictly equal${appositive}:\n\n${jsonDiff}`.trim(); 36 | } 37 | 38 | type CustomLineDiffGutterMode = 'insertion' | 'deletion' | 'context'; 39 | 40 | function generateCustomLineDiff(actual: string, expected: string): string { 41 | const lineDiff = new LineDiff(expected, actual, 0).toString(); 42 | const lines = lineDiff.split('\n'); 43 | 44 | const modes: CustomLineDiffGutterMode[] = lines.map(fromLineToGutterMode); 45 | const silent = !(modes.find(m => m === 'insertion' || m === 'deletion')); 46 | 47 | if (silent) { 48 | return ''; 49 | } 50 | 51 | const diff: string[] = []; 52 | let buffer: string[] = []; 53 | let inserted: number = 0; 54 | let deleted: number = 0; 55 | 56 | // Collapse diffs. 57 | for (let index = 0; index < lines.length; index++) { 58 | const lineno = index + 1; 59 | const line = lines[index]; 60 | const mode = modes[index]; 61 | 62 | const previousMode: CustomLineDiffGutterMode | void = modes[index - 1]; 63 | const nextMode: CustomLineDiffGutterMode | void = modes[index + 1]; 64 | 65 | if (mode === previousMode && mode === nextMode && mode === 'context') { 66 | continue; 67 | } 68 | if (mode === 'insertion') { 69 | deleted += 1; 70 | } 71 | if (mode === 'deletion') { 72 | inserted += 1; 73 | } 74 | buffer.push(line); 75 | 76 | if (buffer.length && (index === lines.length - 1 || mode === 'context')) { 77 | const context = `@@ -${lineno + 1},${deleted} +${lineno + 1},${inserted} @@`; 78 | diff.push(...([context].concat(buffer))); 79 | buffer = []; 80 | inserted = deleted = 0; 81 | } 82 | } 83 | 84 | return diff.join('\n'); 85 | } 86 | 87 | function fromLineToGutterMode(line: string): CustomLineDiffGutterMode { 88 | const gutter = line.substring(0, 3); // `line-diff` gutter is 3 characters 89 | if (gutter === ' + ') { 90 | return 'insertion'; 91 | } 92 | if (gutter === ' - ') { 93 | return 'deletion'; 94 | } 95 | return 'context'; 96 | } 97 | -------------------------------------------------------------------------------- /src/util/selectors.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { FirstMateSelector } from '../scopes'; 4 | import { FastScopeSelector } from './fast-selector'; 5 | 6 | type ScopeSelector = FastScopeSelector | FirstMateSelector; 7 | 8 | export class TextmateScopeSelector { 9 | public readonly isArray: boolean; 10 | private selector: ScopeSelector[] | ScopeSelector; 11 | 12 | constructor(public readonly source?: string[] | string) { 13 | if (Array.isArray(source)) { 14 | this.selector = source.map(optimizedSelectorFactory); 15 | } 16 | if (typeof source === 'string') { 17 | this.selector = optimizedSelectorFactory(source); 18 | } 19 | } 20 | 21 | public match(scopes: string[] | string): boolean { 22 | if (!this.selector) { 23 | return false; 24 | } 25 | if (Array.isArray(this.selector)) { 26 | return this.selector.some(s => s.matches(scopes)); 27 | } 28 | if (this.selector) { 29 | return this.selector.matches(scopes); 30 | } 31 | } 32 | 33 | public include(scopes: string[][]): boolean { 34 | if (!this.selector) { 35 | return false; 36 | } 37 | return scopes.some(this.match.bind(this) as (s: string | string[]) => boolean); 38 | } 39 | 40 | public toString(): string { 41 | return Array.isArray(this.source) ? this.source.join(', ') : String(this.source); 42 | } 43 | } 44 | 45 | function isSelectorAtScopeLevel(selector: string) { 46 | return ( 47 | /[a-zA-Z0-9+_]$/.test(selector) && 48 | /^[a-zA-Z0-9+_]/.test(selector) && 49 | !/[^a-zA-Z0-9+_\-.]/.test(selector) 50 | ); 51 | } 52 | 53 | function optimizedSelectorFactory(selector: string): ScopeSelector { 54 | try { 55 | return isSelectorAtScopeLevel(selector) 56 | ? new FastScopeSelector(selector) 57 | : new FirstMateSelector(selector); 58 | } catch (e) { 59 | throw new Error(`'${selector}' is an invalid Textmate scope selector. ${e && (e as Error).message || ''}`.trim()); 60 | } 61 | } 62 | 63 | export class TextmateScopeSelectorMap { 64 | private matchers: Record; 65 | 66 | constructor(public readonly sourcemap: Record | undefined) { 67 | this.matchers = {}; 68 | if (typeof sourcemap === 'object' && sourcemap?.constructor === Object) { 69 | for (const key in sourcemap) { 70 | if (Object.prototype.hasOwnProperty.call(sourcemap, key)) { 71 | this.matchers[key] = optimizedSelectorFactory(key); 72 | } 73 | } 74 | } 75 | } 76 | 77 | public key(scopes: string | string[]): string | void { 78 | if (!this.sourcemap) { 79 | return void 0; 80 | } 81 | for (const key in this.sourcemap) { 82 | if (this.matchers[key].matches(scopes)) { 83 | return key; 84 | } 85 | } 86 | return void 0; 87 | } 88 | 89 | public has(scopes: string | string[]): boolean { 90 | return typeof this.key(scopes) === 'string'; 91 | } 92 | 93 | public value(scopes: string | string[]): number | void { 94 | const key = this.key(scopes); 95 | if (!this.sourcemap || !key) { 96 | return void 0; 97 | } 98 | return this.sourcemap[key]; 99 | } 100 | 101 | public toString(): string { 102 | return JSON.stringify(this.sourcemap); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/suite/api/language-contribution.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | 'use strict'; 4 | 5 | import * as vscode from 'vscode'; 6 | 7 | import { strictEqual } from '../../util/assert'; 8 | import TextmateLanguageService from '../../../src/main'; 9 | 10 | const { 11 | getGrammarContribution, 12 | getLanguageContribution, 13 | getLanguageConfiguration, 14 | getContributorExtension 15 | } = TextmateLanguageService.api; 16 | 17 | const languageExtensionMap: Record = { 18 | matlab: '.m', 19 | mediawiki: '.mediawiki', 20 | typescript: '.ts', 21 | vue: '.vue', 22 | }; 23 | 24 | const languageScopeNameMap: Record = { 25 | matlab: 'source.matlab', 26 | mediawiki: 'text.html.mediawiki', 27 | typescript: 'source.ts', 28 | vue: 'source.vue', 29 | }; 30 | 31 | const languageContributorMap: Record = { 32 | matlab: 'Gimly81.matlab', 33 | mediawiki: 'sndst00m.mediawiki', 34 | typescript: 'vscode.typescript', 35 | vue: 'sndst00m.vue', 36 | }; 37 | 38 | suite('test/api/language-contribution.test.ts (src/api.ts)', function() { 39 | this.timeout(5000); 40 | 41 | test('getLanguageConfiguration(): Promise', async function() { 42 | void vscode.window.showInformationMessage('API `getLanguageConfiguration` method (src/api.ts)'); 43 | 44 | const languageConfiguration = await getLanguageConfiguration(globalThis.languageId); 45 | 46 | strictEqual(languageConfiguration.wordPattern instanceof RegExp, ['typescript', 'vue'].includes(globalThis.languageId)); 47 | 48 | strictEqual(Array.isArray(languageConfiguration.brackets), true); 49 | 50 | strictEqual(Array.isArray(languageConfiguration.comments.blockComment), true); 51 | }); 52 | 53 | test('getLanguageContribution(): LanguageDefinition', function() { 54 | void vscode.window.showInformationMessage('API `getScopeInformationAtPosition` method (src/api.ts)'); 55 | 56 | const languageContribution = getLanguageContribution(globalThis.languageId); 57 | 58 | strictEqual(languageContribution.id, globalThis.languageId); 59 | 60 | const languageFileExtension = languageExtensionMap[globalThis.languageId]; 61 | strictEqual(languageContribution.extensions?.includes(languageFileExtension), true); 62 | }); 63 | 64 | test('getGrammarContribution(): GrammarLanguageDefinition', function() { 65 | void vscode.window.showInformationMessage('API `getScopeInformationAtPosition` method (src/api.ts)'); 66 | 67 | const grammarContribution = getGrammarContribution(globalThis.languageId); 68 | 69 | strictEqual(grammarContribution.language, globalThis.languageId); 70 | 71 | const languageScopeName = languageScopeNameMap[globalThis.languageId]; 72 | strictEqual(grammarContribution.scopeName, languageScopeName); 73 | }); 74 | 75 | test('getContributorExtension(): vscode.Extension | void', function() { 76 | void vscode.window.showInformationMessage('API `getContributorExtension` method (src/api.ts)'); 77 | 78 | const extension = getContributorExtension(globalThis.languageId); 79 | 80 | strictEqual(typeof extension === 'object', true); 81 | 82 | const languageContributorId = languageContributorMap[globalThis.languageId]; 83 | strictEqual((extension || {}).id, languageContributorId); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/patches/vscode-matlab/textmate-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "assignment": { 3 | "separator": "punctuation.separator.comma.matlab - parens", 4 | "single": "meta.assignment.variable.single.matlab", 5 | "multiple": "meta.assignment.variable.group.matlab" 6 | }, 7 | "declarations": [ 8 | "meta.*.declaration entity.name", 9 | "meta.assignment.definition entity.name", 10 | "comment.line.double-percentage entity.name.section" 11 | ], 12 | "dedentation": [ 13 | "keyword.control.elseif.matlab", 14 | "keyword.control.else.matlab" 15 | ], 16 | "exclude": "{external,mpm-packages,syntaxes,grammars,snap}/**", 17 | "indentation": { 18 | "punctuation.definition.comment.begin.matlab": 1, 19 | "punctuation.definition.comment.end.matlab": -1, 20 | "keyword.control.for.matlab": 1, 21 | "keyword.control.end.for.matlab": -1, 22 | "keyword.control.if.matlab": 1, 23 | "keyword.control.end.if.matlab": -1, 24 | "keyword.control.else.matlab": -1, 25 | "keyword.control.elseif.matlab": -1, 26 | "keyword.control.repeat.parallel.matlab": 1, 27 | "keyword.control.end.repeat.parallel.matlab": -1, 28 | "keyword.control.switch.matlab": 1, 29 | "keyword.control.end.switch.matlab": -1, 30 | "keyword.control.try.matlab": 1, 31 | "keyword.control.end.try.matlab": -1, 32 | "keyword.control.while.matlab": 1, 33 | "keyword.control.end.while.matlab": -1, 34 | "storage.type.class.matlab": 1, 35 | "storage.type.class.end.matlab": -1, 36 | "keyword.control.enum.matlab": 1, 37 | "keyword.control.end.enum.matlab": -1, 38 | "keyword.control.events.matlab": 1, 39 | "keyword.control.end.events.matlab": -1, 40 | "keyword.control.methods.matlab": 1, 41 | "keyword.control.end.methods.matlab": -1, 42 | "keyword.control.properties.matlab": 1, 43 | "keyword.control.end.properties.matlab": -1, 44 | "storage.type.function.matlab": 1, 45 | "storage.type.function.end.matlab": -1, 46 | "keyword.control.arguments.matlab": 1, 47 | "keyword.control.end.arguments.matlab": -1 48 | }, 49 | "punctuation": { 50 | "continuation": "punctuation.separator.continuation.line" 51 | }, 52 | "markers": { 53 | "start": "^\\s*#?region\\b", 54 | "end": "^\\s*#?end\\s?region\\b" 55 | }, 56 | "symbols": { 57 | "meta.for keyword.control.for": 2, 58 | "meta.if keyword.control.if": 2, 59 | "meta.else keyword.control.else": 2, 60 | "meta.elseif keyword.control.elseif": 2, 61 | "meta.repeat.parallel keyword.control.repeat.parallel": 2, 62 | "meta.switch keyword.control.switch": 2, 63 | "meta.try keyword.control.try": 2, 64 | "meta.while keyword.control.while": 2, 65 | "meta.class entity.name.type.class": 4, 66 | "meta.enum variable.other.enummember": 21, 67 | "meta.events entity.name.type.event": 23, 68 | "meta.enum keyword.control.enum": 2, 69 | "meta.events keyword.control.events": 2, 70 | "meta.methods keyword.control.methods": 2, 71 | "meta.properties keyword.control.properties": 2, 72 | "meta.function.declaration entity.name.function": 11, 73 | "meta.assignment.definition.property variable.object.property": 6, 74 | "comment.line.double-percentage entity.name.section": 14, 75 | "meta.assignment.variable.single variable.other.readwrite": 12, 76 | "meta.assignment.variable.group variable.other.readwrite": 12 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/scopes/index.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) GitHub Inc. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as parser from './parser'; 8 | 9 | import type { ParsedMatcher, GroupPrefix } from './matchers'; 10 | 11 | export class FirstMateSelector { 12 | private _cache: Record = {}; 13 | private _prefixes: Record = {}; 14 | private matcher: ParsedMatcher; 15 | 16 | /** 17 | * Create a new scope selector. 18 | * 19 | * @param {string} source The string to parse as a scope selector. 20 | * @return A newly constructed ParsedSelector. 21 | */ 22 | constructor(public readonly source: string) { 23 | this.matcher = parser.parse(source); 24 | } 25 | 26 | /** 27 | * Check if this scope selector matches the scopes. 28 | * 29 | * @param {string|string[]} scopes A single scope or an array of them to be compared against. 30 | * @return {boolean} Whether or not this ParsedSelector matched. 31 | */ 32 | public matches(scopes: string | string[]): boolean { 33 | if (typeof scopes === 'string') { 34 | scopes = [scopes]; 35 | } 36 | const target = scopes.join(' '); 37 | const entry = this._cache[target]; 38 | 39 | if (typeof entry !== 'undefined') { 40 | return entry; 41 | } else { 42 | const result = this.matcher.matches(scopes); 43 | return (this._cache[target] = result); 44 | } 45 | } 46 | 47 | /** 48 | * Gets the prefix of this scope selector. 49 | * 50 | * @param {string|string[]} scopes The scopes to match a prefix against. 51 | * @return {string|undefined} The matching prefix, if there is one. 52 | */ 53 | public getPrefix(scopes: string | string[]): GroupPrefix | undefined { 54 | if (typeof scopes === 'string') { 55 | scopes = [scopes]; 56 | } 57 | const target = typeof scopes === 'string' ? scopes : scopes.join(' '); 58 | const entry = this._prefixes[target]; 59 | 60 | if (typeof entry !== 'undefined') { 61 | return entry === null ? undefined : entry; 62 | } else { 63 | const result = this.matcher.getPrefix(scopes) || null; 64 | this._prefixes[target] = result; 65 | return result; 66 | } 67 | } 68 | 69 | /** 70 | * Gets the priority of this scope selector. 71 | * 72 | * @param {string|string[]} scopes The scopes to match a priority against. 73 | * @return {string|undefined} The matching priority, if there is one. 74 | */ 75 | public getPriority(scopes: string | string[]): number { 76 | switch (this.getPrefix(scopes)) { 77 | case 'L': // left - before non-prefixed rules 78 | return -1; 79 | case 'R': // right - after non-prefixed rules 80 | return 1; 81 | default: 82 | return 0; 83 | } 84 | } 85 | 86 | public toString(): string { 87 | return this.source; 88 | } 89 | } 90 | 91 | export namespace rules { 92 | export type Start = parser.Start; 93 | export type Atom = parser.Atom; 94 | export type Scope = parser.Scope; 95 | export type Path = parser.Path; 96 | export type Group = parser.Group; 97 | export type Expression = parser.Expression; 98 | export type Composite = parser.Composite; 99 | export type Selector = parser.Selector; 100 | } 101 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "blockComment": [ 4 | "" 6 | ] 7 | }, 8 | "brackets": [ 9 | [ 10 | "" 12 | ], 13 | [ 14 | "<", 15 | ">" 16 | ], 17 | [ 18 | "{", 19 | "}" 20 | ], 21 | [ 22 | "(", 23 | ")" 24 | ] 25 | ], 26 | "autoClosingPairs": [ 27 | // html 28 | { 29 | "open": "{", 30 | "close": "}" 31 | }, 32 | { 33 | "open": "[", 34 | "close": "]" 35 | }, 36 | { 37 | "open": "(", 38 | "close": ")" 39 | }, 40 | { 41 | "open": "'", 42 | "close": "'" 43 | }, 44 | { 45 | "open": "\"", 46 | "close": "\"" 47 | }, 48 | { 49 | "open": "", 51 | "notIn": [ 52 | "comment", 53 | "string" 54 | ] 55 | }, 56 | // javascript 57 | { 58 | "open": "`", 59 | "close": "`", 60 | "notIn": [ 61 | "string", 62 | "comment" 63 | ] 64 | }, 65 | { 66 | "open": "/**", 67 | "close": " */", 68 | "notIn": [ 69 | "string" 70 | ] 71 | } 72 | ], 73 | "autoCloseBefore": ";:.,=}])><`'\" \n\t", 74 | "surroundingPairs": [ 75 | // html 76 | { 77 | "open": "'", 78 | "close": "'" 79 | }, 80 | { 81 | "open": "\"", 82 | "close": "\"" 83 | }, 84 | { 85 | "open": "{", 86 | "close": "}" 87 | }, 88 | { 89 | "open": "[", 90 | "close": "]" 91 | }, 92 | { 93 | "open": "(", 94 | "close": ")" 95 | }, 96 | { 97 | "open": "<", 98 | "close": ">" 99 | }, 100 | // javascript 101 | [ 102 | "`", 103 | "`" 104 | ], 105 | ], 106 | "colorizedBracketPairs": [], 107 | "folding": { 108 | "markers": { 109 | "start": "^\\s*", 110 | "end": "^\\s*" 111 | } 112 | }, 113 | "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\s]+)", 114 | "onEnterRules": [ 115 | { 116 | "beforeText": { 117 | "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script|style))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$", 118 | "flags": "i" 119 | }, 120 | "afterText": { 121 | "pattern": "^<\\/([_:\\w][_:\\w-.\\d]*)\\s*>", 122 | "flags": "i" 123 | }, 124 | "action": { 125 | "indent": "indentOutdent" 126 | } 127 | }, 128 | { 129 | "beforeText": { 130 | "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script|style))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$", 131 | "flags": "i" 132 | }, 133 | "action": { 134 | "indent": "indent" 135 | } 136 | } 137 | ], 138 | // https://github.com/vuejs/language-tools/issues/1762 139 | "indentationRules": { 140 | // "increaseIndentPattern": "<(?!\\?|(?:area|base|br|col|frame|hr|html|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script|style)\\b|[^>]*\\/>)([-_\\.A-Za-z0-9]+)(?=\\s|>)\\b[^>]*>(?!.*<\\/\\1>)|)|\\{[^}\"']*$", 141 | // add (?!\\s*\\() to fix https://github.com/vuejs/language-tools/issues/1847#issuecomment-1246101071 142 | "increaseIndentPattern": "<(?!\\?|(?:area|base|br|col|frame|hr|html|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script|style)\\b|[^>]*\\/>)([-_\\.A-Za-z0-9]+)(?=\\s|>)\\b[^>]*>(?!\\s*\\()(?!.*<\\/\\1>)|)|\\{[^}\"']*$", 143 | "decreaseIndentPattern": "^\\s*(<\\/(?!html)[-_\\.A-Za-z0-9]+\\b[^>]*>|-->|\\})" 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /test/suite/services/selectors.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { strictEqual } from '../../util/assert'; 5 | 6 | import { getComponentSampleDataUri } from '../../util/files'; 7 | import { loadJsonFile, TextmateScopeSelector, TextmateScopeSelectorMap } from '../../util/common'; 8 | 9 | // Add types for JSON test data to ease development. 10 | import type * as selectorJson from '../../data/selectors/selector.json'; 11 | type SelectorTestData = typeof selectorJson; 12 | import type * as mapJson from '../../data/selectors/map.json'; 13 | type SelectorMapTestData = typeof mapJson; 14 | 15 | suite('test/suite/selectors.util.test.ts - TextmateScopeSelector class (src/../util/selectors.ts)', function() { 16 | test('TextmateScopeSelector.match(scopes) - Macromates spec', async function() { 17 | void vscode.window.showInformationMessage('TextmateScopeSelector class (src/utils/selectors.ts)'); 18 | const tests = await scopeInput(); 19 | 20 | for (const [feature, cases] of tests) { 21 | for (const t of cases) { 22 | const selector = new TextmateScopeSelector(t.selector); 23 | strictEqual(selector.match(t.input), t.expected, `TextmateScopeSelector ${feature}: "${t.selector}"`); 24 | } 25 | } 26 | }); 27 | }); 28 | 29 | async function scopeInput() { 30 | const data = getComponentSampleDataUri('selectors', 'selector'); 31 | const json = await loadJsonFile(data); 32 | const tests = Object.entries(json); 33 | return tests; 34 | } 35 | 36 | suite('test/suite/selectors.util.test.ts - TextmateScopeSelectorMap class (src/utils/selectors.ts)', function() { 37 | test('TextmateScopeSelectorMap.key(scopes)', async function() { 38 | void vscode.window.showInformationMessage('TextmateScopeSelectorMap class (src/utils/selectors.ts)'); 39 | const testCases = await mapInput(); 40 | 41 | for (const t of testCases) { 42 | strictEqual( 43 | t.map.key(t.input), 44 | t.key === null ? void 0 : t.key, 45 | 'TextmateScopeSelectorMap.key; sourcemap: ' + t.map.toString() + ', scopes: "' + t.scopes + '"' 46 | ); 47 | } 48 | }); 49 | 50 | test('TextmateScopeSelectorMap.has(scopes)', async function() { 51 | const testCases = await mapInput(); 52 | for (const t of testCases) { 53 | strictEqual( 54 | t.map.has(t.input), 55 | t.expected, 56 | 'TextmateScopeSelectorMap.has; sourcemap "' + t.map.toString() + '", scopes: "' + t.scopes + '"' 57 | ); 58 | } 59 | }); 60 | 61 | test('TextmateScopeSelectorMap.value(scopes)', async function() { 62 | const testCases = await mapInput(); 63 | for (const t of testCases) { 64 | strictEqual( 65 | t.map.value(t.input), 66 | t.value === null ? void 0 : t.value, 67 | 'TextmateScopeSelectorMap.value; sourcemap: "' + t.map.toString() + '", scopes: "' + t.scopes + '"' 68 | ); 69 | } 70 | }); 71 | }); 72 | 73 | async function mapInput() { 74 | const data = getComponentSampleDataUri('selectors', 'map'); 75 | const json = await loadJsonFile(data); 76 | 77 | type SelectorMapTestDatum = SelectorMapTestData[number]; 78 | interface SelectorMapTestCase extends Pick { 79 | key: string | void; 80 | value: number | void; 81 | map: typeof TextmateScopeSelectorMap.prototype; 82 | scopes: string; 83 | } 84 | 85 | const testCases: SelectorMapTestCase[] = []; 86 | for (const d of json) { 87 | const key = d.key === null ? void 0 : d.key; 88 | const value = d.value === null ? void 0 : d.value; 89 | const map = new TextmateScopeSelectorMap(d.key === null ? void 0 : { [d.key]: d.value }); 90 | const scopes = d.input.join(' '); 91 | testCases.push({ ...d, key, map, scopes, value }); 92 | } 93 | 94 | return testCases; 95 | } 96 | -------------------------------------------------------------------------------- /test/data/outline/Snake.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anchor": 2, 4 | "level": 0, 5 | "line": 0, 6 | "location": { 7 | "uri": "./samples/Snake.m", 8 | "range": [ 9 | { 10 | "line": 0, 11 | "character": 9 12 | }, 13 | { 14 | "line": 22, 15 | "character": 0 16 | } 17 | ] 18 | }, 19 | "text": "Snake", 20 | "token": "entity.name.type.class.matlab", 21 | "type": 4 22 | }, 23 | { 24 | "anchor": 8, 25 | "level": 1, 26 | "line": 1, 27 | "location": { 28 | "uri": "./samples/Snake.m", 29 | "range": [ 30 | { 31 | "line": 1, 32 | "character": 4 33 | }, 34 | { 35 | "line": 22, 36 | "character": 0 37 | } 38 | ] 39 | }, 40 | "text": "methods", 41 | "token": "keyword.control.methods.matlab", 42 | "type": 2 43 | }, 44 | { 45 | "anchor": 16, 46 | "level": 2, 47 | "line": 2, 48 | "location": { 49 | "uri": "./samples/Snake.m", 50 | "range": [ 51 | { 52 | "line": 2, 53 | "character": 23 54 | }, 55 | { 56 | "line": 4, 57 | "character": 11 58 | } 59 | ] 60 | }, 61 | "text": "Snake", 62 | "token": "entity.name.function.matlab", 63 | "type": 11 64 | }, 65 | { 66 | "anchor": 55, 67 | "level": 2, 68 | "line": 5, 69 | "location": { 70 | "uri": "./samples/Snake.m", 71 | "range": [ 72 | { 73 | "line": 5, 74 | "character": 23 75 | }, 76 | { 77 | "line": 7, 78 | "character": 11 79 | } 80 | ] 81 | }, 82 | "text": "noise", 83 | "token": "entity.name.function.matlab", 84 | "type": 11 85 | }, 86 | { 87 | "anchor": 76, 88 | "level": 2, 89 | "line": 8, 90 | "location": { 91 | "uri": "./samples/Snake.m", 92 | "range": [ 93 | { 94 | "line": 8, 95 | "character": 23 96 | }, 97 | { 98 | "line": 15, 99 | "character": 11 100 | } 101 | ] 102 | }, 103 | "text": "move", 104 | "token": "entity.name.function.matlab", 105 | "type": 11 106 | }, 107 | { 108 | "anchor": 84, 109 | "level": 3, 110 | "line": 9, 111 | "location": { 112 | "uri": "./samples/Snake.m", 113 | "range": [ 114 | { 115 | "line": 9, 116 | "character": 12 117 | }, 118 | { 119 | "line": 10, 120 | "character": 37 121 | } 122 | ] 123 | }, 124 | "text": "if", 125 | "token": "keyword.control.if.matlab", 126 | "type": 2 127 | }, 128 | { 129 | "anchor": 100, 130 | "level": 3, 131 | "line": 11, 132 | "location": { 133 | "uri": "./samples/Snake.m", 134 | "range": [ 135 | { 136 | "line": 11, 137 | "character": 12 138 | }, 139 | { 140 | "line": 15, 141 | "character": 11 142 | } 143 | ] 144 | }, 145 | "text": "else", 146 | "token": "keyword.control.else.matlab", 147 | "type": 2 148 | }, 149 | { 150 | "anchor": 135, 151 | "level": 2, 152 | "line": 16, 153 | "location": { 154 | "uri": "./samples/Snake.m", 155 | "range": [ 156 | { 157 | "line": 16, 158 | "character": 23 159 | }, 160 | { 161 | "line": 22, 162 | "character": 0 163 | } 164 | ] 165 | }, 166 | "text": "sleep", 167 | "token": "entity.name.function.matlab", 168 | "type": 11 169 | } 170 | ] 171 | -------------------------------------------------------------------------------- /test/extensions/vscode-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "displayName": "Vue", 4 | "description": "Vue token service wiring for Textmate language service.", 5 | "version": "0.0.1", 6 | "publisher": "sndst00m", 7 | "engines": { 8 | "vscode": "^1.51.1" 9 | }, 10 | "categories": [ 11 | "Programming Languages" 12 | ], 13 | "activationEvents": [ 14 | "onLanguage:typescript" 15 | ], 16 | "main": "./out/src/extension.js", 17 | "browser": "./out/src/extension.js", 18 | "contributes": { 19 | "languages": [ 20 | { 21 | "id": "vue", 22 | "extensions": [ 23 | ".vue" 24 | ], 25 | "configuration": "./language-configuration.json" 26 | } 27 | ], 28 | "grammars": [ 29 | { 30 | "language": "vue", 31 | "scopeName": "source.vue", 32 | "path": "./syntaxes/vue.tmLanguage.json", 33 | "embeddedLanguages": { 34 | "source.vue": "vue", 35 | "text": "plaintext", 36 | "text.html.derivative": "html", 37 | "text.html.markdown": "markdown", 38 | "text.pug": "jade", 39 | "source.css": "css", 40 | "source.css.scss": "scss", 41 | "source.css.less": "less", 42 | "source.sass": "sass", 43 | "source.stylus": "stylus", 44 | "source.postcss": "postcss", 45 | "source.js": "javascript", 46 | "source.ts": "typescript", 47 | "source.js.jsx": "javascriptreact", 48 | "source.tsx": "typescriptreact", 49 | "source.coffee": "coffeescript", 50 | "meta.tag.js": "jsx-tags", 51 | "meta.tag.tsx": "jsx-tags", 52 | "meta.tag.without-attributes.js": "jsx-tags", 53 | "meta.tag.without-attributes.tsx": "jsx-tags", 54 | "source.json": "json", 55 | "source.json.comments": "jsonc", 56 | "source.json5": "json5", 57 | "source.yaml": "yaml", 58 | "source.toml": "toml", 59 | "source.graphql": "graphql" 60 | }, 61 | "unbalancedBracketScopes": [ 62 | "keyword.operator.relational", 63 | "storage.type.function.arrow", 64 | "keyword.operator.bitwise.shift", 65 | "meta.brace.angle", 66 | "punctuation.definition.tag" 67 | ] 68 | }, 69 | { 70 | "scopeName": "vue.directives", 71 | "path": "./syntaxes/vue-directives.json", 72 | "injectTo": [ 73 | "source.vue", 74 | "text.html.markdown", 75 | "text.html.derivative", 76 | "text.pug" 77 | ] 78 | }, 79 | { 80 | "scopeName": "vue.interpolations", 81 | "path": "./syntaxes/vue-interpolations.json", 82 | "injectTo": [ 83 | "source.vue", 84 | "text.html.markdown", 85 | "text.html.derivative", 86 | "text.pug" 87 | ] 88 | }, 89 | { 90 | "scopeName": "vue.sfc.style.variable.injection", 91 | "path": "./syntaxes/vue-sfc-style-variable-injection.json", 92 | "injectTo": [ 93 | "source.vue" 94 | ] 95 | } 96 | ] 97 | }, 98 | "scripts": { 99 | "vscode:prepublish": "npm run compile", 100 | "compile": "tsc -p ./", 101 | "watch": "tsc -watch -p ./", 102 | "pretest": "npm run compile", 103 | "test": "echo \"Error: no test specified\" && exit 1" 104 | }, 105 | "devDependencies": { 106 | "@types/vscode": "^1.55.0", 107 | "@vscode/test-electron": "^2.3.6", 108 | "@vscode/test-web": "^0.0.63", 109 | "mocha": "^10.2.0", 110 | "typescript": "^4.9.5" 111 | }, 112 | "dependencies": { 113 | "vscode-textmate-languageservice": "^4.0.0" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/data/selectors/map.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "input": [ 4 | "source.matlab", 5 | "meta.function.matlab", 6 | "meta.if.matlab", 7 | "keyword.control.if.matlab" 8 | ], 9 | "expected": true, 10 | "key": "meta.if keyword.control.if", 11 | "value": 2 12 | }, 13 | { 14 | "input": [ 15 | "source.matlab", 16 | "meta.class.matlab", 17 | "meta.class.declaration.matlab", 18 | "entity.name.type.class.matlab" 19 | ], 20 | "expected": true, 21 | "key": "meta.class entity.name.type.class", 22 | "value": 4 23 | }, 24 | { 25 | "input": [ 26 | "source.matlab", 27 | "meta.class.matlab", 28 | "meta.enum.matlab", 29 | "keyword.control.enum.matlab" 30 | ], 31 | "expected": true, 32 | "key": "meta.enum keyword.control.enum", 33 | "value": 2 34 | }, 35 | { 36 | "input": [ 37 | "source.matlab", 38 | "meta.class.matlab", 39 | "meta.enum.matlab", 40 | "meta.assignment.definition.enummember.matlab", 41 | "variable.other.enummember.matlab" 42 | ], 43 | "expected": true, 44 | "key": "meta.enum variable.other.enummember", 45 | "value": 21 46 | }, 47 | { 48 | "input": [ 49 | "source.matlab", 50 | "meta.class.matlab", 51 | "meta.events.matlab", 52 | "meta.assignment.definition.event.matlab", 53 | "entity.name.type.event.matlab" 54 | ], 55 | "expected": true, 56 | "key": "meta.events entity.name.type.event", 57 | "value": 23 58 | }, 59 | { 60 | "input": [ 61 | "source.matlab", 62 | "meta.function.matlab", 63 | "meta.function.declaration.matlab", 64 | "entity.name.function.matlab" 65 | ], 66 | "expected": true, 67 | "key": "meta.function.declaration entity.name.function", 68 | "value": 11 69 | }, 70 | { 71 | "input": [ 72 | "source.matlab", 73 | "meta.class.matlab", 74 | "meta.properties.matlab", 75 | "meta.assignment.definition.property.matlab", 76 | "variable.object.property.matlab" 77 | ], 78 | "expected": true, 79 | "key": "meta.assignment.definition.property variable.object.property", 80 | "value": 6 81 | }, 82 | { 83 | "input": [ 84 | "source.matlab", 85 | "comment.line.double-percentage.matlab", 86 | "entity.name.section.matlab" 87 | ], 88 | "expected": true, 89 | "key": "comment.line.double-percentage entity.name.section", 90 | "value": 14 91 | }, 92 | { 93 | "input": [ 94 | "source.matlab", 95 | "meta.function.matlab", 96 | "meta.assignment.variable.group.matlab", 97 | "variable.other.readwrite.matlab" 98 | ], 99 | "expected": true, 100 | "key": "meta.assignment.variable.group variable.other.readwrite", 101 | "value": 12 102 | }, 103 | { 104 | "input": [ 105 | "source.matlab", 106 | "meta.function.matlab", 107 | "meta.assignment.variable.single.matlab", 108 | "variable.other.readwrite.matlab" 109 | ], 110 | "expected": true, 111 | "key": "meta.assignment.variable.single variable.other.readwrite", 112 | "value": 12 113 | }, 114 | { 115 | "input": [ 116 | "source.matlab", 117 | "meta.function.matlab", 118 | "meta.function.declaration.matlab", 119 | "meta.assignment.variable.output.matlab", 120 | "variable.parameter.output.matlab" 121 | ], 122 | "expected": false, 123 | "key": null, 124 | "value": null 125 | }, 126 | { 127 | "input": [ 128 | "source.matlab", 129 | "meta.function.matlab", 130 | "meta.function.declaration.matlab", 131 | "meta.parameters.matlab", 132 | "variable.parameter.input.matlab" 133 | ], 134 | "expected": false, 135 | "key": null, 136 | "value": null 137 | } 138 | ] 139 | -------------------------------------------------------------------------------- /test/data/selectors/selector.json: -------------------------------------------------------------------------------- 1 | { 2 | "atom~asterisk": [ 3 | { "selector": "*", "input": ["a"], "expected": true }, 4 | { "selector": "*", "input": ["b", "c"], "expected": true }, 5 | { "selector": "a.*.c", "input": ["a.b.c"], "expected": true }, 6 | { "selector": "a.*.c", "input": ["a.b.c.d"], "expected": true }, 7 | { "selector": "a.*.c", "input": ["a.b.d.c"], "expected": false } 8 | ], 9 | "atom": [ 10 | { "selector": "a", "input": ["a"], "expected": true }, 11 | { "selector": "a", "input": ["a.b"], "expected": true }, 12 | { "selector": "a.b", "input": ["a.b.c"], "expected": true }, 13 | { "selector": "a", "input": ["abc"], "expected": false }, 14 | { "selector": "a.b-c", "input": ["a.b-c.d"], "expected": true }, 15 | { "selector": "a.b", "input": ["a.b-d"], "expected": false }, 16 | { "selector": "c++", "input": ["c++"], "expected": true }, 17 | { "selector": "c++", "input": ["c"], "expected": false }, 18 | { "selector": "a_b_c", "input": ["a_b_c"], "expected": true }, 19 | { "selector": "a_b_c", "input": ["a_b"], "expected": false } 20 | ], 21 | "path~prefix": [ 22 | { "selector": "R:g", "input": ["g"], "expected": true }, 23 | { "selector": "R:g", "input": ["R:g"], "expected": false } 24 | ], 25 | "composite~operator~| (disjunct)": [ 26 | { "selector": "a | b", "input": ["b"], "expected": true }, 27 | { "selector": "a|b|c", "input": ["c"], "expected": true }, 28 | { "selector": "a|b|c", "input": ["d"], "expected": false } 29 | ], 30 | "composite~operator~- (negation)": [ 31 | { "selector": "a - c", "input": ["a", "b"], "expected": true }, 32 | { "selector": "a - c", "input": ["a"], "expected": true }, 33 | { "selector": "-c", "input": ["b"], "expected": true }, 34 | { "selector": "-c", "input": ["c", "b"], "expected": false }, 35 | { "selector": "a-b", "input": ["a", "b"], "expected": false }, 36 | { "selector": "a -b", "input": ["a", "b"], "expected": false }, 37 | { "selector": "a -c", "input": ["a", "b"], "expected": true }, 38 | { "selector": "a-c", "input": ["a", "b"], "expected": false } 39 | ], 40 | "composite~operator~& (conjunction)": [ 41 | { "selector": "a & b", "input": ["b", "a"], "expected": true }, 42 | { "selector": "a&b&c", "input": ["c"], "expected": false }, 43 | { "selector": "a&b&c", "input": ["a", "b", "d"], "expected": false }, 44 | { "selector": "a & -b", "input": ["a", "b", "d"], "expected": false }, 45 | { "selector": "a & -b", "input": ["a", "d"], "expected": true } 46 | ], 47 | "selector~,": [ 48 | { "selector": "a,b,c", "input": ["b", "c"], "expected": true }, 49 | { "selector": "a, b, c", "input": ["d", "e"], "expected": false }, 50 | { "selector": "a, b, c", "input": ["d", "c.e"], "expected": true }, 51 | { "selector": "a,", "input": ["a", "c"], "expected": true }, 52 | { "selector": "a,", "input": ["b", "c"], "expected": false } 53 | ], 54 | "group~()": [ 55 | { "selector": "(a,b) | (c, d)", "input": ["a"], "expected": true }, 56 | { "selector": "(a,b) | (c, d)", "input": ["b"], "expected": true }, 57 | { "selector": "(a,b) | (c, d)", "input": ["c"], "expected": true }, 58 | { "selector": "(a,b) | (c, d)", "input": ["d"], "expected": true }, 59 | { "selector": "(a,b) | (c, d)", "input": ["e"], "expected": false } 60 | ], 61 | "path~others~_ (descendant)": [ 62 | { "selector": "a b", "input": ["a", "b"], "expected": true }, 63 | { "selector": "a b", "input": ["b", "a"], "expected": false }, 64 | { "selector": "a c", "input": ["a", "b", "c", "d", "e"], "expected": true }, 65 | { "selector": "a b e", "input": ["a", "b", "c", "d", "e"], "expected": true } 66 | ], 67 | "matches~scope:string": [ 68 | { "selector": "a|b", "input": "a", "expected": true }, 69 | { "selector": "a|b", "input": "b", "expected": true }, 70 | { "selector": "a|b", "input": "c", "expected": false }, 71 | { "selector": "test", "input": "test", "expected": true } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /test/data/document-symbol/Dog.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Dog", 4 | "detail": "entity.name.type.class.matlab", 5 | "kind": 4, 6 | "range": [ 7 | { 8 | "line": 0, 9 | "character": 9 10 | }, 11 | { 12 | "line": 18, 13 | "character": 0 14 | } 15 | ], 16 | "selectionRange": [ 17 | { 18 | "line": 0, 19 | "character": 9 20 | }, 21 | { 22 | "line": 18, 23 | "character": 0 24 | } 25 | ], 26 | "children": [ 27 | { 28 | "name": "methods", 29 | "detail": "keyword.control.methods.matlab", 30 | "kind": 2, 31 | "range": [ 32 | { 33 | "line": 1, 34 | "character": 4 35 | }, 36 | { 37 | "line": 18, 38 | "character": 0 39 | } 40 | ], 41 | "selectionRange": [ 42 | { 43 | "line": 1, 44 | "character": 4 45 | }, 46 | { 47 | "line": 18, 48 | "character": 0 49 | } 50 | ], 51 | "children": [ 52 | { 53 | "name": "Dog", 54 | "detail": "entity.name.function.matlab", 55 | "kind": 11, 56 | "range": [ 57 | { 58 | "line": 2, 59 | "character": 23 60 | }, 61 | { 62 | "line": 4, 63 | "character": 11 64 | } 65 | ], 66 | "selectionRange": [ 67 | { 68 | "line": 2, 69 | "character": 23 70 | }, 71 | { 72 | "line": 4, 73 | "character": 11 74 | } 75 | ], 76 | "children": [] 77 | }, 78 | { 79 | "name": "noise", 80 | "detail": "entity.name.function.matlab", 81 | "kind": 11, 82 | "range": [ 83 | { 84 | "line": 5, 85 | "character": 23 86 | }, 87 | { 88 | "line": 7, 89 | "character": 11 90 | } 91 | ], 92 | "selectionRange": [ 93 | { 94 | "line": 5, 95 | "character": 23 96 | }, 97 | { 98 | "line": 7, 99 | "character": 11 100 | } 101 | ], 102 | "children": [] 103 | }, 104 | { 105 | "name": "move", 106 | "detail": "entity.name.function.matlab", 107 | "kind": 11, 108 | "range": [ 109 | { 110 | "line": 8, 111 | "character": 23 112 | }, 113 | { 114 | "line": 11, 115 | "character": 11 116 | } 117 | ], 118 | "selectionRange": [ 119 | { 120 | "line": 8, 121 | "character": 23 122 | }, 123 | { 124 | "line": 11, 125 | "character": 11 126 | } 127 | ], 128 | "children": [] 129 | }, 130 | { 131 | "name": "sleep", 132 | "detail": "entity.name.function.matlab", 133 | "kind": 11, 134 | "range": [ 135 | { 136 | "line": 12, 137 | "character": 23 138 | }, 139 | { 140 | "line": 18, 141 | "character": 0 142 | } 143 | ], 144 | "selectionRange": [ 145 | { 146 | "line": 12, 147 | "character": 23 148 | }, 149 | { 150 | "line": 18, 151 | "character": 0 152 | } 153 | ], 154 | "children": [] 155 | } 156 | ] 157 | } 158 | ] 159 | } 160 | ] 161 | -------------------------------------------------------------------------------- /test/data/outline/Horse.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anchor": 2, 4 | "level": 0, 5 | "line": 0, 6 | "location": { 7 | "uri": "./samples/Horse.m", 8 | "range": [ 9 | { 10 | "line": 0, 11 | "character": 9 12 | }, 13 | { 14 | "line": 24, 15 | "character": 0 16 | } 17 | ] 18 | }, 19 | "text": "Horse", 20 | "token": "entity.name.type.class.matlab", 21 | "type": 4 22 | }, 23 | { 24 | "anchor": 8, 25 | "level": 1, 26 | "line": 1, 27 | "location": { 28 | "uri": "./samples/Horse.m", 29 | "range": [ 30 | { 31 | "line": 1, 32 | "character": 4 33 | }, 34 | { 35 | "line": 24, 36 | "character": 0 37 | } 38 | ] 39 | }, 40 | "text": "methods", 41 | "token": "keyword.control.methods.matlab", 42 | "type": 2 43 | }, 44 | { 45 | "anchor": 16, 46 | "level": 2, 47 | "line": 2, 48 | "location": { 49 | "uri": "./samples/Horse.m", 50 | "range": [ 51 | { 52 | "line": 2, 53 | "character": 23 54 | }, 55 | { 56 | "line": 4, 57 | "character": 11 58 | } 59 | ] 60 | }, 61 | "text": "Horse", 62 | "token": "entity.name.function.matlab", 63 | "type": 11 64 | }, 65 | { 66 | "anchor": 55, 67 | "level": 2, 68 | "line": 5, 69 | "location": { 70 | "uri": "./samples/Horse.m", 71 | "range": [ 72 | { 73 | "line": 5, 74 | "character": 23 75 | }, 76 | { 77 | "line": 7, 78 | "character": 11 79 | } 80 | ] 81 | }, 82 | "text": "noise", 83 | "token": "entity.name.function.matlab", 84 | "type": 11 85 | }, 86 | { 87 | "anchor": 76, 88 | "level": 2, 89 | "line": 8, 90 | "location": { 91 | "uri": "./samples/Horse.m", 92 | "range": [ 93 | { 94 | "line": 8, 95 | "character": 23 96 | }, 97 | { 98 | "line": 17, 99 | "character": 11 100 | } 101 | ] 102 | }, 103 | "text": "move", 104 | "token": "entity.name.function.matlab", 105 | "type": 11 106 | }, 107 | { 108 | "anchor": 84, 109 | "level": 3, 110 | "line": 9, 111 | "location": { 112 | "uri": "./samples/Horse.m", 113 | "range": [ 114 | { 115 | "line": 9, 116 | "character": 12 117 | }, 118 | { 119 | "line": 10, 120 | "character": 36 121 | } 122 | ] 123 | }, 124 | "text": "if", 125 | "token": "keyword.control.if.matlab", 126 | "type": 2 127 | }, 128 | { 129 | "anchor": 100, 130 | "level": 3, 131 | "line": 11, 132 | "location": { 133 | "uri": "./samples/Horse.m", 134 | "range": [ 135 | { 136 | "line": 11, 137 | "character": 12 138 | }, 139 | { 140 | "line": 12, 141 | "character": 36 142 | } 143 | ] 144 | }, 145 | "text": "elseif", 146 | "token": "keyword.control.elseif.matlab", 147 | "type": 2 148 | }, 149 | { 150 | "anchor": 116, 151 | "level": 3, 152 | "line": 13, 153 | "location": { 154 | "uri": "./samples/Horse.m", 155 | "range": [ 156 | { 157 | "line": 13, 158 | "character": 12 159 | }, 160 | { 161 | "line": 17, 162 | "character": 11 163 | } 164 | ] 165 | }, 166 | "text": "else", 167 | "token": "keyword.control.else.matlab", 168 | "type": 2 169 | }, 170 | { 171 | "anchor": 151, 172 | "level": 2, 173 | "line": 18, 174 | "location": { 175 | "uri": "./samples/Horse.m", 176 | "range": [ 177 | { 178 | "line": 18, 179 | "character": 23 180 | }, 181 | { 182 | "line": 24, 183 | "character": 0 184 | } 185 | ] 186 | }, 187 | "text": "sleep", 188 | "token": "entity.name.function.matlab", 189 | "type": 11 190 | } 191 | ] 192 | -------------------------------------------------------------------------------- /test/data/outline/Cat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anchor": 2, 4 | "level": 0, 5 | "line": 0, 6 | "location": { 7 | "uri": "./samples/Cat.m", 8 | "range": [ 9 | { 10 | "line": 0, 11 | "character": 9 12 | }, 13 | { 14 | "line": 18, 15 | "character": 0 16 | } 17 | ] 18 | }, 19 | "text": "Cat", 20 | "token": "entity.name.type.class.matlab", 21 | "type": 4 22 | }, 23 | { 24 | "anchor": 8, 25 | "level": 1, 26 | "line": 1, 27 | "location": { 28 | "uri": "./samples/Cat.m", 29 | "range": [ 30 | { 31 | "line": 1, 32 | "character": 4 33 | }, 34 | { 35 | "line": 18, 36 | "character": 0 37 | } 38 | ] 39 | }, 40 | "text": "methods", 41 | "token": "keyword.control.methods.matlab", 42 | "type": 2 43 | }, 44 | { 45 | "anchor": 16, 46 | "level": 2, 47 | "line": 2, 48 | "location": { 49 | "uri": "./samples/Cat.m", 50 | "range": [ 51 | { 52 | "line": 2, 53 | "character": 23 54 | }, 55 | { 56 | "line": 4, 57 | "character": 11 58 | } 59 | ] 60 | }, 61 | "text": "Cat", 62 | "token": "entity.name.function.matlab", 63 | "type": 11 64 | }, 65 | { 66 | "anchor": 55, 67 | "level": 2, 68 | "line": 5, 69 | "location": { 70 | "uri": "./samples/Cat.m", 71 | "range": [ 72 | { 73 | "line": 5, 74 | "character": 23 75 | }, 76 | { 77 | "line": 7, 78 | "character": 11 79 | } 80 | ] 81 | }, 82 | "text": "noise", 83 | "token": "entity.name.function.matlab", 84 | "type": 11 85 | }, 86 | { 87 | "anchor": 75, 88 | "level": 2, 89 | "line": 8, 90 | "location": { 91 | "uri": "./samples/Cat.m", 92 | "range": [ 93 | { 94 | "line": 8, 95 | "character": 23 96 | }, 97 | { 98 | "line": 11, 99 | "character": 11 100 | } 101 | ] 102 | }, 103 | "text": "move", 104 | "token": "entity.name.function.matlab", 105 | "type": 11 106 | }, 107 | { 108 | "anchor": 114, 109 | "level": 2, 110 | "line": 12, 111 | "location": { 112 | "uri": "./samples/Cat.m", 113 | "range": [ 114 | { 115 | "line": 12, 116 | "character": 23 117 | }, 118 | { 119 | "line": 18, 120 | "character": 0 121 | } 122 | ] 123 | }, 124 | "text": "sleep", 125 | "token": "entity.name.function.matlab", 126 | "type": 11 127 | }, 128 | { 129 | "anchor": 152, 130 | "level": 0, 131 | "line": 19, 132 | "location": { 133 | "uri": "./samples/Cat.m", 134 | "range": [ 135 | { 136 | "line": 19, 137 | "character": 3 138 | }, 139 | { 140 | "line": 23, 141 | "character": 0 142 | } 143 | ] 144 | }, 145 | "text": "Test header folding and level=0&line!=0", 146 | "token": "entity.name.section.matlab", 147 | "type": 14 148 | }, 149 | { 150 | "anchor": 155, 151 | "level": 1, 152 | "line": 20, 153 | "location": { 154 | "uri": "./samples/Cat.m", 155 | "range": [ 156 | { 157 | "line": 20, 158 | "character": 9 159 | }, 160 | { 161 | "line": 20, 162 | "character": 33 163 | } 164 | ] 165 | }, 166 | "text": "handleCatNoiseKo", 167 | "token": "entity.name.function.matlab", 168 | "type": 11 169 | }, 170 | { 171 | "anchor": 160, 172 | "level": 1, 173 | "line": 21, 174 | "location": { 175 | "uri": "./samples/Cat.m", 176 | "range": [ 177 | { 178 | "line": 21, 179 | "character": 4 180 | }, 181 | { 182 | "line": 23, 183 | "character": 0 184 | } 185 | ] 186 | }, 187 | "text": "if", 188 | "token": "keyword.control.if.matlab", 189 | "type": 2 190 | } 191 | ] 192 | -------------------------------------------------------------------------------- /test/suite/providers/definition.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { strictEqual } from '../../util/assert'; 5 | 6 | import { isWebRuntime } from '../../util/runtime'; 7 | import { tokenServicePromise, documentServicePromise, definitionProviderPromise } from '../../util/factory'; 8 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 9 | import { runSamplePass } from '../../util/bench'; 10 | 11 | import type { TextmateToken } from '../../../src/services/tokenizer'; 12 | import { TextmateScopeSelector } from '../../util/common'; 13 | 14 | suite('test/suite/definition.test.ts - TextmateDefinitionProvider class (src/definition.ts)', function() { 15 | this.timeout(10000); 16 | 17 | test('TextmateDefinitionProvider.provideDefinition(): Promise<[vscode.Location,...vscode.Location[]]>', async function() { 18 | // Early exit + pass if we are in web runtime. 19 | if (isWebRuntime) { 20 | this.skip(); 21 | } 22 | 23 | void vscode.window.showInformationMessage('TextmateDefinitionProvider class (src/definition.ts)'); 24 | const results = await definitionProviderResult(); 25 | 26 | for (let index = 0; index < results.length; index++) { 27 | const page = results[index]; 28 | const filename = `${BASENAMES[globalThis.languageId][index]}.m`; 29 | 30 | for (const entry of page) { 31 | strictEqual(entry.definition instanceof Object, true, filename); 32 | } 33 | } 34 | }); 35 | 36 | test('TextmateDefinitionProvider.provideDefinition(): Promise', async function() { 37 | // Early exit + pass if we are in web runtime. 38 | if (isWebRuntime) { 39 | this.skip(); 40 | } 41 | 42 | const results = await definitionProviderResult(); 43 | 44 | let error: TypeError | void = void 0; 45 | for (let index = 0; index < results.length; index++) { 46 | const page = results[index]; 47 | const basename = BASENAMES[globalThis.languageId][index]; 48 | 49 | try { 50 | await runSamplePass('definition', basename, page); 51 | } catch (e) { 52 | error = typeof error !== 'undefined' ? error : e as Error; 53 | } 54 | } 55 | if (error) { 56 | throw error; 57 | } 58 | }); 59 | 60 | this.afterAll(async function() { 61 | await vscode.commands.executeCommand('workbench.action.closeAllEditors'); 62 | }); 63 | }); 64 | 65 | async function definitionProviderResult() { 66 | const documentService = await documentServicePromise; 67 | const tokenizer = await tokenServicePromise; 68 | const definitionProvider = await definitionProviderPromise; 69 | 70 | if (BASENAMES[globalThis.languageId].length === 1) { 71 | return []; 72 | } 73 | 74 | const classReferenceSelector = new TextmateScopeSelector([ 75 | 'meta.inherited-class entity.name.type.class', 76 | 'meta.method-call entity.name.type.class' 77 | ]); 78 | 79 | function isBaseClassReference(token: TextmateToken) { 80 | return classReferenceSelector.match(token.scopes) && token.text === BASENAMES.matlab[0]; 81 | } 82 | 83 | interface DefinitionTestResult extends TextmateToken { 84 | uri: vscode.Uri; 85 | definition: vscode.Location | void; 86 | } 87 | 88 | const samples = BASENAMES[globalThis.languageId].map(getSampleFileUri); 89 | const results: DefinitionTestResult[][] = []; 90 | 91 | for (const resource of samples) { 92 | const document = await documentService.getDocument(resource); 93 | const tokens = await tokenizer.fetch(document); 94 | 95 | const activeEditor = await vscode.window.showTextDocument(document); 96 | 97 | const symbols = tokens.filter(isBaseClassReference); 98 | 99 | const page: DefinitionTestResult[] = []; 100 | 101 | // Query each instance of the base class name (`Animal`) in the sample MATLAB file. 102 | for (const symbol of symbols) { 103 | const startPosition = new vscode.Position(symbol.line, symbol.startIndex); 104 | const endPosition = new vscode.Position(symbol.line, symbol.endIndex); 105 | 106 | activeEditor.selection = new vscode.Selection(startPosition, endPosition); 107 | 108 | const locations = await definitionProvider.provideDefinition(document, startPosition); 109 | 110 | page.push({ ...symbol, definition: locations[0], uri: resource }); 111 | } 112 | results.push(page); 113 | } 114 | 115 | return results; 116 | } 117 | -------------------------------------------------------------------------------- /test/suite/api/token-information.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | 'use strict'; 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | import { strictEqual } from '../../util/assert'; 9 | import TextmateLanguageService from '../../../src/main'; 10 | import { documentServicePromise, tokenServicePromise } from '../../util/factory'; 11 | import { BASENAMES, getSampleFileUri } from '../../util/files'; 12 | import { TextmateScopeSelector } from '../../util/common'; 13 | 14 | const { 15 | getScopeInformationAtPosition, 16 | getScopeRangeAtPosition, 17 | getTokenInformationAtPosition 18 | } = TextmateLanguageService.api; 19 | let titleData: Awaited>; 20 | 21 | type TextmateScopeSelectorType = typeof TextmateScopeSelector.prototype; 22 | 23 | const languageScopeMap: Record = { 24 | matlab: 'entity.name.type.class.matlab', 25 | mediawiki: 'string.quoted.other.heading.mediawiki', 26 | typescript: 'entity.name.type.class.ts', 27 | vue: 'entity.name.type.class.ts', 28 | }; 29 | 30 | const languageSelectorMap: Record = 31 | Object.fromEntries( 32 | Object.entries(languageScopeMap) 33 | .map(([k, v]) => [k, new TextmateScopeSelector(v)]) 34 | ); 35 | 36 | const languageStandardTypeMap = { 37 | matlab: vscode.StandardTokenType.Other, 38 | mediawiki: vscode.StandardTokenType.String, 39 | typescript: vscode.StandardTokenType.Other, 40 | vue: vscode.StandardTokenType.Other, 41 | }; 42 | 43 | suite('test/api/token-information.test.ts (src/api.ts)', function() { 44 | this.timeout(5000); 45 | 46 | this.beforeAll(async function() { 47 | titleData = await getTitleData(); 48 | }); 49 | 50 | test('getScopeInformationAtPosition(): Promise', async function() { 51 | void vscode.window.showInformationMessage('API `getScopeInformationAtPosition` method (src/api.ts)'); 52 | 53 | const scopeInformation = await getScopeInformationAtPosition(titleData.document, titleData.position); 54 | 55 | if (globalThis.languageId === 'mediawiki') { 56 | this.skip(); 57 | } 58 | 59 | strictEqual(scopeInformation.line, 0); 60 | strictEqual(scopeInformation.text, titleData.basename); 61 | 62 | strictEqual(scopeInformation.startIndex, titleData.token.startIndex); 63 | strictEqual(scopeInformation.endIndex, titleData.token.endIndex); 64 | 65 | strictEqual(scopeInformation.level, 0); 66 | 67 | const scopeType = languageScopeMap[globalThis.languageId]; 68 | strictEqual(scopeInformation.type, scopeType); 69 | }); 70 | 71 | test('getScopeRangeAtPosition(): Promise', async function() { 72 | void vscode.window.showInformationMessage('API `getScopeRangeAtPosition` method (src/api.ts)'); 73 | 74 | const scopeRange = await getScopeRangeAtPosition(titleData.document, titleData.position); 75 | 76 | if (globalThis.languageId === 'mediawiki') { 77 | this.skip(); 78 | } 79 | 80 | strictEqual(scopeRange.isEqual(titleData.range), true); 81 | }); 82 | 83 | test('getScopeInformationAtPosition(): Promise', async function() { 84 | void vscode.window.showInformationMessage('API `getScopeInformationAtPosition` method (src/api.ts)'); 85 | 86 | const tokenInformation = await getTokenInformationAtPosition(titleData.document, titleData.position); 87 | 88 | if (globalThis.languageId === 'mediawiki') { 89 | this.skip(); 90 | } 91 | 92 | strictEqual(tokenInformation.range.isEqual(titleData.range), true); 93 | 94 | const standardType = languageStandardTypeMap[globalThis.languageId]; 95 | strictEqual(tokenInformation.type, standardType); 96 | }); 97 | }); 98 | 99 | async function getTitleData() { 100 | const documentService = await documentServicePromise; 101 | const tokenService = await tokenServicePromise; 102 | 103 | const basename = BASENAMES[globalThis.languageId][0]; 104 | const resource = getSampleFileUri(basename); 105 | 106 | const document = await documentService.getDocument(resource); 107 | const tokens = await tokenService.fetch(document); 108 | 109 | const selector = languageSelectorMap[globalThis.languageId]; 110 | const token = tokens.find(t => t.line === 0 && selector.match(t.scopes)); 111 | 112 | const position = new vscode.Position(token.line, token.startIndex); 113 | const range = new vscode.Range(token.line, token.startIndex, token.line, token.endIndex); 114 | 115 | return { basename, document, position, range, token }; 116 | } 117 | -------------------------------------------------------------------------------- /src/services/tokenizer.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscodeTextmate from 'vscode-textmate'; 4 | 5 | import { ServiceBase } from '../util/service'; 6 | 7 | import type * as vscode from 'vscode'; 8 | import type { Mutable } from 'type-fest'; 9 | import type { ConfigData } from '../config'; 10 | 11 | export interface TextmateToken extends Mutable { 12 | level: number; 13 | line: number; 14 | text: string; 15 | type: string; 16 | } 17 | 18 | export interface TextmateTokenizeLineResult extends Omit { 19 | readonly tokens: TextmateToken[]; 20 | } 21 | 22 | interface TextmateTokenizerState { 23 | delta: number; 24 | continuation: boolean; 25 | declaration: boolean; 26 | line: number; 27 | rule: vscodeTextmate.StateStack; 28 | stack: number; 29 | } 30 | 31 | export class TokenizerService extends ServiceBase { 32 | private _states: Record = {}; 33 | 34 | constructor( 35 | private _config: ConfigData, 36 | private _grammar: vscodeTextmate.IGrammar 37 | ) { 38 | super(); 39 | } 40 | 41 | public async parse(document: vscode.TextDocument): Promise { 42 | const tokens: TextmateToken[] = []; 43 | 44 | const state = this._states[document.uri.path] = {} as TextmateTokenizerState; 45 | state.delta = 0; 46 | state.continuation = false; 47 | state.declaration = false; 48 | state.line = 0; 49 | state.rule = vscodeTextmate.INITIAL; 50 | state.stack = 0; 51 | 52 | for (let lineNumber = 0; lineNumber < document.lineCount; lineNumber++) { 53 | const line: vscode.TextLine = document.lineAt(lineNumber); 54 | const lineResult = this._grammar.tokenizeLine(line.text, state.rule) as TextmateTokenizeLineResult; 55 | 56 | for (const token of lineResult.tokens) { 57 | token.type = token.scopes[token.scopes.length - 1]; 58 | token.text = line.text.substring(token.startIndex, token.endIndex); 59 | token.line = lineNumber; 60 | } 61 | 62 | for (let index = 0; index < (lineResult.tokens.length - 1); index++) { 63 | const token = lineResult.tokens[index]; 64 | const nextToken = lineResult.tokens[index + 1]; 65 | 66 | if ( 67 | ( 68 | this._config.selectors.assignment.single.match(token.scopes) 69 | && this._config.selectors.assignment.single.match(nextToken.scopes) 70 | ) 71 | || ( 72 | this._config.selectors.assignment.multiple.match(token.scopes) 73 | && this._config.selectors.assignment.multiple.match(nextToken.scopes) 74 | && !this._config.selectors.assignment.separator.match(nextToken.scopes) 75 | ) 76 | ) { 77 | token.endIndex = nextToken.endIndex; 78 | token.text += nextToken.text; 79 | lineResult.tokens.splice(index + 1, 1); 80 | index--; 81 | } 82 | } 83 | 84 | for (let index = 0; index < lineResult.tokens.length; index++) { 85 | const token = lineResult.tokens[index]; 86 | const delta = this._config.selectors.indentation.value(token.scopes) || 0; 87 | const isIndentToken = delta > 0; 88 | const isDedentToken = delta < 0; 89 | const isRedentToken = this._config.selectors.dedentation.match(token.scopes); 90 | const isDeclarationToken = isIndentToken || isRedentToken; 91 | const isContinuationToken = this._config.selectors.punctuation.continuation.match(token.scopes); 92 | 93 | if (state.declaration === false) { 94 | if (isDedentToken) { 95 | state.stack += delta; 96 | let subindex = index - 1; 97 | while (subindex >= 0) { 98 | lineResult.tokens[subindex].level += delta; 99 | subindex -= 1; 100 | } 101 | } 102 | if (isDeclarationToken) { 103 | state.delta += Math.abs(delta); 104 | } 105 | } 106 | 107 | if (state.declaration && !isRedentToken) { // handle redent e.g. ELSE-IF clause 108 | state.delta += delta; 109 | } 110 | 111 | state.declaration = state.declaration || isDeclarationToken; 112 | state.continuation = state.continuation || isContinuationToken; 113 | 114 | if (state.declaration && lineNumber > state.line) { 115 | if (state.continuation === false) { 116 | state.stack += state.delta; 117 | state.delta = 0; 118 | state.declaration = false; 119 | } else { 120 | state.continuation = false; 121 | } 122 | } 123 | 124 | token.level = state.stack; 125 | state.line = lineNumber; 126 | } 127 | 128 | state.rule = lineResult.ruleStack; 129 | tokens.push(...lineResult.tokens); 130 | } 131 | 132 | delete this._states[document.uri.path]; 133 | return Promise.resolve(tokens); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/samples/List.wiki: -------------------------------------------------------------------------------- 1 | = List = 2 | This page gives a '''list of domesticated animals''',{{cite web|url=http://archaeology.about.com/od/dterms/a/domestication.htm|title=How did we ever manage to domesticate so many animals?}} also including a list of animals that have an extensive relationship with humans beyond simple predation. This includes species which are semi-domesticated, undomesticated but [[Captive breeding|captive bred]] on a commercial scale, or commonly wild-caught, at least occasionally captive-bred, and [[Tame animal|tameable]]. In order to be considered fully domesticated, most species have undergone significant [[Heredity|genetic]], [[Behavior|behavioural]] and [[Morphology (biology)|morphological]] changes from their wild ancestors, while others have changed very little from their wild ancestors despite hundreds or thousands of years of potential [[selective breeding]]. A number of factors determine how quickly any changes may occur in a species, but there is not always a desire to improve a species from its wild form. Domestication is a gradual process, so there is no precise moment in the history of a given species when it can be considered to have become fully domesticated. 3 | 4 | [[Zooarchaeology]] has identified three classes of animal domesticates: 5 | # [[Pets]] ([[dogs]], [[cats]], [[ferrets]], [[hamsters]], etc.) 6 | # [[Livestock]] ([[cattle]], [[sheep]], [[pigs]], [[goats]], etc.) 7 | # [[Beasts of burden]] ([[horses]], [[camels]], [[donkeys]], etc.){{cite journal |doi=10.1086/659964 |title=The Origins of Agriculture: New Data, New Ideas |journal=Current Anthropology |volume=52 |pages=S163–S174 |year=2011 |last1=Price |first1=T. Douglas |last2=Bar-Yosef |first2=Ofer |s2cid=128906192 |url=https://semanticscholar.org/paper/e769a0398e3b3b0a10d331c1e556d5582f5d2cd2 }} 8 | 9 | == Domesticated animals == 10 | 11 | {| class="wikitable sortable" 12 | ! Species and subspecies 13 | ! Wild ancestor 14 | ! data-sort-type="number" | Date 15 | ! Location of origin 16 | ! Image 17 | ! Degree and type of domestication 18 | ! [[Taxon]] group 19 | |- 20 | | [[Dog|Domestic dog]] (''Canis familiaris'') 21 | | Extinct [[Pleistocene]] population of the [[Wolf|grey wolf]] (''Canis lupus'' ssp.) 22 | | data-sort-value="-29999" |[[Origin of the domestic dog#Morphological divergence|13,000]] [[BCE]] 23 | | style="text-align:center;" |[[China]], 24 | [[Europe]] 25 | | [[File:Pembroke Welsh Corgi 600.jpg|130px]] 26 | | Tame (with exceptions), significant physical changes, probably significant behavioral changes as well 27 | | '''1c''' ''Carnivora'' 28 | |- 29 | | colspan="7" | 30 | * '''Purpose:''' meat, leather, [[Salish Wool Dog|fiber]], hunting, herding, guarding, fighting, racing, working, rescuing, guiding, [[Police dog|policing]], draft, pack, sport, service, therapy, [[Detection dog|narcotics detection]], truffle harvesting, pest control, research, education, show, pets 31 | * '''Extent in the wild vs. captivity:''' Domestic and [[Free-ranging dog|feral dog]]s both very common, ancestor or nearest wild relative less common, but not rare 32 | |- 33 | | [[Cat|Domestic cat]] or house cat (''Felis catus'') 34 | | [[African wildcat|North African wildcat]] (''Felis lybica lybica'') 35 | | data-sort-value="-7900" | 8000–7500 BCE 36 | | style="text-align:center;" |the [[Near East]] 37 | | [[File:Jammlich crop.jpg|130px]] 38 | | Tame, some physical changes 39 | | '''1c''' ''Carnivora'' 40 | |- 41 | | colspan="7" | 42 | * '''Purpose:''' meat, pelts, pest control, research, [[show]], pets 43 | * '''Extent in the wild vs. captivity:''' very abundant in captivity; true wildcat species less abundant, though not rare, [[feral]] populations very common 44 | |- 45 | | [[Horse|Domestic horse]] (''Equus caballus'') 46 | | Extinct unknown population of the [[wild horse]] (''Equus ferus''), possibly the [[tarpan]] or European wild horse (''E. f. ferus'')† 47 | | data-sort-value="-3500" | 3500 BCE 48 | | style="text-align:center;" |[[Kazakhstan]] 49 | | [[File:Nokota Horses cropped.jpg|130px]] 50 | | Tame, some physical changes, mainly in colouration 51 | | '''1e''' ''Other mammals'' 52 | |- 53 | | colspan="7" | 54 | * '''Purpose:''' milk, meat, hair, manure, working, plowing, fighting, racing, servicing, guiding, draft, pack, mount, execution, lawn mowing, weed control, show, pets 55 | * '''Extent in the wild vs. captivity:''' common in captivity, very rare in the wild, feral populations common 56 | |- 57 | | [[Corn snake|Corn snakes]] (''Pantherophis gutttatus''), [[Pantherophis obsoletus|western rat snakes]] (''P. obsoletus''), and [[Gray ratsnake|grey rat snakes]] (''P. spiloides'') 58 | | Possibly ''Lampropeltis'' genus 59 | | the 1960s (uncertain for ''P. obsoletus'' and ''P. spiloides'') 60 | | the [[United States]] 61 | | [[File:Kornnatter.jpg|130x130px]] 62 | | Slight physical changes 63 | | '''3a''' ''Serpentes'' 64 | |- 65 | | colspan=7 | 66 | * '''Purpose:''' ornamental, show, pets 67 | * '''Extent in the wild vs. capacity:''' somewhat common in captivity, common in the wild 68 | | } 69 | -------------------------------------------------------------------------------- /src/services/resolver.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE.md in the project root for license information. 4 | * -------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | import * as vscode from 'vscode'; 8 | import * as vscodeTextmate from 'vscode-textmate'; 9 | import { readFileText, loadMessageBundle } from '../util/loader'; 10 | import { ContributorData, isGrammarLanguageDefinition, plaintextGrammarDefinition } from '../util/contributes'; 11 | import type { GrammarDefinition, GrammarLanguageDefinition, LanguageDefinition } from '../util/contributes'; 12 | 13 | const localize = loadMessageBundle(); 14 | 15 | const plainTextGrammar = { 16 | name: localize('plainText.alias', 'Plain Text'), 17 | patterns: [], 18 | scopeName: 'text' 19 | }; 20 | 21 | export class ResolverService implements vscodeTextmate.RegistryOptions { 22 | private _contributes: ContributorData; 23 | constructor(public onigLib: Promise, context?: vscode.ExtensionContext) { 24 | this._contributes = new ContributorData(context); 25 | } 26 | 27 | public async loadGrammar(scopeName: string): Promise { 28 | if (scopeName === 'text') { 29 | const text = JSON.stringify(plainTextGrammar); 30 | const appRoot = vscode.Uri.file(vscode.env.appRoot); 31 | const jsonPath = vscode.Uri.joinPath(appRoot, plaintextGrammarDefinition.path).path; 32 | return vscodeTextmate.parseRawGrammar(text, jsonPath); 33 | } 34 | 35 | const mapping = this._contributes.sources; 36 | const extension = mapping.grammars[scopeName]; 37 | for (const grammar of this._contributes.grammars.reverse()) { 38 | if (grammar.scopeName !== scopeName) { 39 | continue; 40 | } 41 | 42 | try { 43 | const uri = vscode.Uri.joinPath(extension.extensionUri, grammar.path); 44 | const text = await readFileText(uri); 45 | return vscodeTextmate.parseRawGrammar(text, uri.path); 46 | } catch (e) { 47 | const filepath = extension.extensionUri?.fsPath.replace(/\\/g, '/') || ''; 48 | throw new Error('Could not load grammar "' + grammar.path + '" from extension path "' + filepath + '"'); 49 | } 50 | } 51 | 52 | return null; 53 | } 54 | 55 | public getInjections(scopeName: string): string[] | undefined { 56 | return this._contributes.getInjections(scopeName); 57 | } 58 | 59 | public getEncodedLanguageId(languageId: string): number | undefined { 60 | return this._contributes.getEncodedLanguageId(languageId); 61 | } 62 | 63 | public findLanguageByExtension(fileExtension: string): string { 64 | return this._contributes.findLanguageByExtension(fileExtension); 65 | } 66 | 67 | public findLanguageByFilename(fileName: string): string { 68 | return this._contributes.findLanguageByFilename(fileName); 69 | } 70 | 71 | public findGrammarScopeNameFromFilename(fileName: string): string | null { 72 | return this._contributes.findGrammarScopeNameFromFilename(fileName); 73 | } 74 | 75 | public findLanguageIdFromScopeName(scopeName: string): string { 76 | return this._contributes.findLanguageIdFromScopeName(scopeName); 77 | } 78 | 79 | public getLanguageDefinitionFromId(languageId: string): LanguageDefinition { 80 | return this._contributes.getLanguageDefinitionFromId(languageId); 81 | } 82 | 83 | public getLanguageDefinitionFromFilename(fileName: string): LanguageDefinition { 84 | return this._contributes.getLanguageDefinitionFromFilename(fileName); 85 | } 86 | 87 | public getGrammarDefinitionFromScopeName(scopeName: string): GrammarDefinition { 88 | return this._contributes.getGrammarDefinitionFromScopeName(scopeName); 89 | } 90 | 91 | public getGrammarDefinitionFromLanguageId(languageId: string): GrammarLanguageDefinition { 92 | return this._contributes.getGrammarDefinitionFromLanguageId(languageId); 93 | } 94 | 95 | public getEmbeddedLanguagesFromLanguageId(languageId: string): vscodeTextmate.IEmbeddedLanguagesMap | undefined { 96 | return this._contributes.getEmbeddedLanguagesFromLanguageId(languageId); 97 | } 98 | 99 | public getTokenTypesFromLanguageId(languageId: string): vscodeTextmate.ITokenTypeMap | undefined { 100 | return this._contributes.getTokenTypesFromLanguageId(languageId); 101 | } 102 | 103 | public getExtensionFromLanguageId(languageId: string): vscode.Extension | undefined { 104 | return this._contributes.getExtensionFromLanguageId(languageId); 105 | } 106 | 107 | public getExtensionFromScopeName(scopeName: string): vscode.Extension { 108 | return this._contributes.getExtensionFromScopeName(scopeName); 109 | } 110 | 111 | public async loadGrammarByLanguageId(languageId: string): Promise { 112 | const grammar = this._contributes.grammars 113 | .filter(isGrammarLanguageDefinition) 114 | .find(g => g.language === languageId); 115 | 116 | if (!grammar) { 117 | return null; 118 | } 119 | 120 | return this.loadGrammar(grammar.scopeName); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 'use strict'; 3 | 4 | import * as vscode from 'vscode'; 5 | import { GeneratorService } from './services/generators'; 6 | import { TextmateScopeSelector } from './util/selectors'; 7 | import { ContributorData } from './util/contributes'; 8 | import type { TextmateToken } from './services/tokenizer'; 9 | import type { GrammarLanguageDefinition, LanguageDefinition } from './util/contributes'; 10 | 11 | const generators = new GeneratorService(); 12 | 13 | const commentScopeSelector = new TextmateScopeSelector('comment'); 14 | const stringScopeSelector = new TextmateScopeSelector('string'); 15 | const regexScopeSelector = new TextmateScopeSelector('regex'); 16 | 17 | const contributorData = new ContributorData(); 18 | 19 | /** 20 | * Get token scope information at a specific position (caret line and character number). 21 | * @param {vscode.TextDocument} document Document to be tokenized. 22 | * @param {vscode.Position} position Zero-indexed caret position of token in document. 23 | * @returns {Promise} Promise resolving to token data for scope selected by caret position. 24 | */ 25 | export async function getScopeInformationAtPosition(document: vscode.TextDocument, position: vscode.Position): Promise { 26 | const generator = await generators.fetch(document.languageId); 27 | const tokenService = await generator.initTokenService(); 28 | const tokens = await tokenService.fetch(document); 29 | const caret = tokens.find(findTokenByPosition(position)); 30 | return caret; 31 | }; 32 | 33 | /** 34 | * VS Code compatible performant API for token information at a caret position. 35 | * @param {vscode.TextDocument} document Document to be tokenized. 36 | * @param {vscode.Position} position Zero-indexed caret position of token in document. 37 | * @returns {Promise} Promise resolving to token data compatible with VS Code. 38 | */ 39 | export async function getTokenInformationAtPosition(document: vscode.TextDocument, position: vscode.Position): Promise { 40 | const caret = await getScopeInformationAtPosition(document, position); 41 | const range = new vscode.Range(caret.line, caret.startIndex, caret.line, caret.endIndex); 42 | const type = getTokenTypeFromScope(caret.scopes); 43 | return { range, type }; 44 | }; 45 | 46 | /** 47 | * Get matching scope range of the Textmate token intersecting a caret position. 48 | * @param {vscode.TextDocument} document Document to be tokenized. 49 | * @param {vscode.Position} position Zero-indexed caret position to intersect with. 50 | * @returns {Promise} Promise resolving to character and line number of the range. 51 | */ 52 | export async function getScopeRangeAtPosition(document: vscode.TextDocument, position: vscode.Position): Promise { 53 | const caret = await getScopeInformationAtPosition(document, position); 54 | const range = new vscode.Range(caret.line, caret.startIndex, caret.line, caret.endIndex); 55 | return range; 56 | }; 57 | 58 | /** 59 | * Get the language definition point of a language mode identifier. 60 | * @param {string} languageId Language ID as shown in brackets in "Change Language Mode" panel. 61 | * @returns {LanguageDefinition} Language contribution as configured in source VS Code extension. 62 | */ 63 | export function getLanguageContribution(languageId: string): LanguageDefinition { 64 | return contributorData.getLanguageDefinitionFromId(languageId); 65 | }; 66 | 67 | /** 68 | * Get the language definition point of a language mode identifier. 69 | * @param {string} languageId Language identifier, shown in brackets in "Change Language Mode" panel. 70 | * @returns {GrammarLanguageDefinition} Grammar contribution as configured in source VS Code extension. 71 | */ 72 | export function getGrammarContribution(languageId: string): GrammarLanguageDefinition { 73 | return contributorData.getGrammarDefinitionFromLanguageId(languageId); 74 | }; 75 | 76 | /** 77 | * Get the language point of a language mode identifier. 78 | * @param {string} languageId Language ID as shown in brackets in "Change Language Mode" panel. 79 | * @returns {LanguageDefinition} Language contribution as configured in source VS Code extension. 80 | */ 81 | export function getLanguageConfiguration(languageId: string): Promise { 82 | return contributorData.getLanguageConfigurationFromLanguageId(languageId); 83 | }; 84 | 85 | /** 86 | * Get the VS Code Extension API entry of the extension that contributed a language mode identifier. 87 | * @param {string} languageId Language identifier, shown in brackets in "Change Language Mode" panel. 88 | * @returns {vscode.Extension} Extension API instance that contributed the language. 89 | */ 90 | export function getContributorExtension(languageId: string): vscode.Extension | void { 91 | return contributorData.getExtensionFromLanguageId(languageId); 92 | }; 93 | 94 | function findTokenByPosition(position: vscode.Position) { 95 | return function(t: TextmateToken) { 96 | const start = new vscode.Position(t.line, t.startIndex); 97 | const end = new vscode.Position(t.line, t.endIndex); 98 | return position.isAfterOrEqual(start) && position.isBefore(end); 99 | }; 100 | } 101 | 102 | function getTokenTypeFromScope(scopes: string[]): vscode.StandardTokenType { 103 | switch (true) { 104 | case commentScopeSelector.match(scopes): 105 | return vscode.StandardTokenType.Comment; 106 | case regexScopeSelector.match(scopes): 107 | return vscode.StandardTokenType.RegEx; 108 | case stringScopeSelector.match(scopes): 109 | return vscode.StandardTokenType.String; 110 | default: 111 | return vscode.StandardTokenType.Other; 112 | } 113 | } 114 | --------------------------------------------------------------------------------