From 90ab1d3989d8a78e577317b2e7c01f3c6536c0e8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 13:42:37 +0100 Subject: [PATCH 1/7] Deprecate api endpoint --- CHANGELOG.md | 9 ++++++++ README.md | 16 ++------------- package-lock.json | 10 ++++----- package.json | 2 +- src/errors.ts | 7 +++++++ src/extension.ts | 52 +++++++++++++++-------------------------------- 6 files changed, 40 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e047a..48b7d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to the "vscode-parse-tree" extension will be documented in this file. +## 0.51.0 (12 Mar 2026) + +Removed deprecated api endpoints: + +- `getLanguage` - Replaced by `createQuery` +- `getTreeForUri` - Replaced by `getTree` +- `getNodeAtLocation` - No direct replacement. Use scm queries and `createQuery` instead. +- `registerLanguage` - No replacement. We don't believe anyone is using this. + ## 0.33.0 (22 Apr 2025) ### Enhancements diff --git a/README.md b/README.md index f21051b..c9aed17 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Parse tree -Exposes an api function that can be used to get a parse tree node for a given file location. +Exposes an api function that can be used to get a parse tree for a given file. ## Usage @@ -13,25 +13,13 @@ if (parseTreeExtension == null) { throw new Error("Depends on pokey.parse-tree extension"); } -const { getNodeAtLocation } = await parseTreeExtension.activate(); +const { loadLanguage } = await parseTreeExtension.activate(); ``` Don't forget to add an `extensionDependencies`-entry to `package.json` as described in https://code.visualstudio.com/api/references/vscode-api#extensions. -### Parsing a custom language - -If you'd like to add support for a new language, see the [Adding a new language](#adding-a-new-language) section below. Alternatively, your extension can register a custom language with this extension. Although this is not the preferred way to add a new language, it can be convenient when you have a parser that you don't believe belongs in the main extension. - -Parsing your own language is as simple as registering your `languageId` with an absolute path to your `.wasm` file: - -```ts -const { registerLanguage } = await parseTreeExtension.activate(); - -registerLanguage(languageId, wasmPath); -``` - ## Contributing ### Setup diff --git a/package-lock.json b/package-lock.json index 64e64da..0e0a02f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-tree", - "version": "0.50.0", + "version": "0.51.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "parse-tree", - "version": "0.50.0", + "version": "0.51.0", "license": "MIT", "dependencies": { "semver": "^7.7.4", @@ -5034,9 +5034,9 @@ "license": "MIT" }, "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index cbadeb9..5ad5a41 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "parse-tree", "displayName": "Parse tree", "description": "Access document syntax using tree-sitter", - "version": "0.50.0", + "version": "0.51.0", "publisher": "pokey", "repository": { "type": "git", diff --git a/src/errors.ts b/src/errors.ts index 23fd50c..a7b0dc7 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -13,3 +13,10 @@ export class LanguageStillLoadingError extends Error { this.name = "LanguageStillLoadingError"; } } + +export class DeprecatedError extends Error { + constructor(name: string) { + super(`${name} is deprecated and has been removed from the API`); + this.name = "DeprecatedError"; + } +} diff --git a/src/extension.ts b/src/extension.ts index 71921ec..764c54d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,7 +4,11 @@ import * as semver from "semver"; import * as vscode from "vscode"; import * as treeSitter from "web-tree-sitter"; import { Edit } from "web-tree-sitter"; -import { LanguageStillLoadingError, UnsupportedLanguageError } from "./errors"; +import { + DeprecatedError, + LanguageStillLoadingError, + UnsupportedLanguageError, +} from "./errors"; interface Language { module: string; @@ -294,15 +298,8 @@ export function activate(context: vscode.ExtensionContext) { return { loadLanguage, - /** - * @deprecated - */ - getLanguage(languageId: string): treeSitter.Language | undefined { - console.warn( - "vscode-parse-tree: getLanguage is deprecated, use createQuery(languageId, source) instead.", - ); - validateGetLanguage(languageId); - return languages[languageId]?.parser?.language ?? undefined; + getTree(document: vscode.TextDocument) { + return getTreeForUri(document.uri); }, createQuery( @@ -317,34 +314,17 @@ export function activate(context: vscode.ExtensionContext) { return new treeSitter.Query(language, source); }, - /** - * Register a parser wasm file for a language not supported by this - * extension. Note that {@link wasmPath} must be an absolute path, and - * {@link languageId} must not already have a registered parser. - * @param languageId The VSCode language id that you'd like to register a - * parser for - * @param wasmPath The absolute path to the wasm file for your parser - */ - registerLanguage(languageId: string, wasmPath: string) { - if (languages[languageId] != null) { - throw new Error(`Language id '${languageId}' is already registered`); - } - - languages[languageId] = { module: wasmPath }; - void colorAllOpen(); + getLanguage(): treeSitter.Language | undefined { + throw new DeprecatedError("getLanguage"); }, - - getTree(document: vscode.TextDocument) { - return getTreeForUri(document.uri); + getTreeForUri() { + throw new DeprecatedError("getTreeForUri"); }, - - getTreeForUri, - - getNodeAtLocation(location: vscode.Location) { - return getTreeForUri(location.uri).rootNode.descendantForPosition({ - row: location.range.start.line, - column: location.range.start.character, - }); + getNodeAtLocation() { + throw new DeprecatedError("getNodeAtLocation"); + }, + registerLanguage() { + throw new DeprecatedError("registerLanguage"); }, }; } From 7df02efe47b2583c9f79474b5a5dba16ad17915b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 13:45:51 +0100 Subject: [PATCH 2/7] Switch npm version to v24 lts --- .nvmrc | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- src/global.d.ts | 2 -- src/treeSitter.d.ts | 2 ++ 5 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 src/global.d.ts create mode 100644 src/treeSitter.d.ts diff --git a/.nvmrc b/.nvmrc index 373e6d5..a4a7a41 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v25.0.0 +v24.14.0 diff --git a/package-lock.json b/package-lock.json index 0e0a02f..f4f4310 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@cursorless/tree-sitter-wasms": "0.7.0", "@eslint/js": "^10.0.1", - "@types/node": "^25.3.3", + "@types/node": "^24.12.0", "@types/semver": "^7.7.1", "@types/vscode": "1.98.0", "@vscode/vsce": "^3.7.1", @@ -795,13 +795,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/normalize-package-data": { @@ -5051,9 +5051,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 5ad5a41..ad6d552 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "devDependencies": { "@cursorless/tree-sitter-wasms": "0.7.0", "@eslint/js": "^10.0.1", - "@types/node": "^25.3.3", + "@types/node": "^24.12.0", "@types/semver": "^7.7.1", "@types/vscode": "1.98.0", "@vscode/vsce": "^3.7.1", diff --git a/src/global.d.ts b/src/global.d.ts deleted file mode 100644 index 06d1c5e..0000000 --- a/src/global.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface EmscriptenModule {} diff --git a/src/treeSitter.d.ts b/src/treeSitter.d.ts new file mode 100644 index 0000000..7c42b98 --- /dev/null +++ b/src/treeSitter.d.ts @@ -0,0 +1,2 @@ +// Necessary because of faulty type in web-tree-sitter +type EmscriptenModule = unknown; From b9a54c1c60a1679fd0510a761954c4acc8ce6a32 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 13:48:05 +0100 Subject: [PATCH 3/7] Update eslint dependency version --- package-lock.json | 178 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 91 insertions(+), 91 deletions(-) diff --git a/package-lock.json b/package-lock.json index f4f4310..1ea4618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,12 +19,12 @@ "@types/semver": "^7.7.1", "@types/vscode": "1.98.0", "@vscode/vsce": "^3.7.1", - "eslint": "^10.0.2", + "eslint": "^10.0.3", "eslint-config-prettier": "^10.1.8", "jiti": "^2.6.1", "prettier": "^3.8.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.56.1" + "typescript-eslint": "^8.57.0" }, "engines": { "vscode": "^1.98.0" @@ -278,15 +278,15 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", - "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", + "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.2", + "@eslint/object-schema": "^3.0.3", "debug": "^4.3.1", - "minimatch": "^10.2.1" + "minimatch": "^10.2.4" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -345,9 +345,9 @@ } }, "node_modules/@eslint/core": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", - "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -379,9 +379,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", - "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", + "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -389,13 +389,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", - "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", + "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.0", + "@eslint/core": "^1.1.1", "levn": "^0.4.1" }, "engines": { @@ -833,17 +833,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -856,7 +856,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -872,16 +872,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "engines": { @@ -897,14 +897,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "engines": { @@ -919,14 +919,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -937,9 +937,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", "dev": true, "license": "MIT", "engines": { @@ -954,15 +954,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -979,9 +979,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", "dev": true, "license": "MIT", "engines": { @@ -993,16 +993,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1060,16 +1060,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1084,13 +1084,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.57.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2153,18 +2153,18 @@ } }, "node_modules/eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", - "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", + "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.2", + "@eslint/config-array": "^0.23.3", "@eslint/config-helpers": "^0.5.2", - "@eslint/core": "^1.1.0", - "@eslint/plugin-kit": "^0.6.0", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2173,7 +2173,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.1", + "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.1.1", "esquery": "^1.7.0", @@ -2186,7 +2186,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.1", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2225,9 +2225,9 @@ } }, "node_modules/eslint-scope": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", - "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5003,16 +5003,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", - "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", + "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.56.1", - "@typescript-eslint/parser": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1" + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index ad6d552..607b19a 100644 --- a/package.json +++ b/package.json @@ -101,10 +101,10 @@ "@types/vscode": "1.98.0", "@vscode/vsce": "^3.7.1", "eslint-config-prettier": "^10.1.8", - "eslint": "^10.0.2", + "eslint": "^10.0.3", "jiti": "^2.6.1", "prettier": "^3.8.1", - "typescript-eslint": "^8.56.1", + "typescript-eslint": "^8.57.0", "typescript": "^5.9.3" } } From 71f7f391db26d2bd830dd472e94ebe1a173fda40 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 14:45:00 +0100 Subject: [PATCH 4/7] Refactoring --- src/Trees.ts | 64 +++++++++++ src/errors.ts | 6 +- src/extension.ts | 276 +++++++++++++++++++---------------------------- src/types.ts | 6 ++ src/utils.ts | 9 ++ 5 files changed, 194 insertions(+), 167 deletions(-) create mode 100644 src/Trees.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts diff --git a/src/Trees.ts b/src/Trees.ts new file mode 100644 index 0000000..414e1bb --- /dev/null +++ b/src/Trees.ts @@ -0,0 +1,64 @@ +import type { Position, TextDocumentChangeEvent } from "vscode"; +import { Edit, type Parser, type Point, type Tree } from "web-tree-sitter"; + +export class Trees { + private trees: Map = new Map(); + + get(uri: string): Tree | undefined { + return this.trees.get(uri); + } + + set(uri: string, tree: Tree): void { + this.trees.set(uri, tree); + } + + delete(uri: string): void { + this.trees.delete(uri); + } + + updateTree(parser: Parser, edit: TextDocumentChangeEvent) { + if (edit.contentChanges.length === 0) { + return; + } + + const uriString = edit.document.uri.toString(); + const old = this.trees.get(uriString); + + if (old == null) { + throw new Error(`No existing tree for ${uriString}`); + } + + for (const e of edit.contentChanges) { + const startIndex = e.rangeOffset; + const oldEndIndex = e.rangeOffset + e.rangeLength; + const newEndIndex = e.rangeOffset + e.text.length; + const startPos = edit.document.positionAt(startIndex); + const oldEndPos = edit.document.positionAt(oldEndIndex); + const newEndPos = edit.document.positionAt(newEndIndex); + const startPosition = asPoint(startPos); + const oldEndPosition = asPoint(oldEndPos); + const newEndPosition = asPoint(newEndPos); + const delta = new Edit({ + startIndex, + oldEndIndex, + newEndIndex, + startPosition, + oldEndPosition, + newEndPosition, + }); + old.edit(delta); + } + + const tree = parser.parse(edit.document.getText(), old); + + if (tree == null) { + throw Error(`Failed to parse ${uriString}`); + } + + this.trees.set(uriString, tree); + } +} + +function asPoint(pos: Position): Point { + return { row: pos.line, column: pos.character }; +} diff --git a/src/errors.ts b/src/errors.ts index a7b0dc7..687fbe2 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -7,10 +7,10 @@ export class UnsupportedLanguageError extends Error { } } -export class LanguageStillLoadingError extends Error { +export class LanguageFailedToLoad extends Error { constructor(language: string) { - super(`Language '${language}' is still loading; please wait and try again`); - this.name = "LanguageStillLoadingError"; + super(`Language '${language}' failed to load`); + this.name = "LanguageFailedToLoad"; } } diff --git a/src/extension.ts b/src/extension.ts index 764c54d..a54e872 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,21 +3,18 @@ import * as path from "path"; import * as semver from "semver"; import * as vscode from "vscode"; import * as treeSitter from "web-tree-sitter"; -import { Edit } from "web-tree-sitter"; import { DeprecatedError, - LanguageStillLoadingError, + LanguageFailedToLoad, UnsupportedLanguageError, } from "./errors"; - -interface Language { - module: string; - parser?: treeSitter.Parser; -} +import { Trees } from "./Trees"; +import { isDocumentVisible } from "./utils"; +import type { Language } from "./types"; /* eslint-disable @typescript-eslint/naming-convention */ -// Be sure to declare the language in package.json and include a minimalist grammar. +// Be sure to declare the language in package.json const languages: Record = { "java-properties": { module: "tree-sitter-properties" }, "talon-list": { module: "tree-sitter-talon" }, @@ -74,11 +71,9 @@ const languages: Record = { // Fix: this isn't a field, suppress package member coloring like Go const initParser = treeSitter.Parser.init(); -// Called when the extension is first activated by user opening a file with the appropriate language export function activate(context: vscode.ExtensionContext) { - console.debug("Activating tree-sitter..."); // Parse of all visible documents - const trees: Record = {}; + const trees = new Trees(); /** * FIXME: On newer vscode versions some Tree sitter parser throws memory errors @@ -105,34 +100,27 @@ export function activate(context: vscode.ExtensionContext) { */ async function loadLanguage(languageId: string) { const language = languages[languageId]; + + // Language without a parser, e.g. plaintext if (language == null) { return false; } + + // Already loaded if (language.parser != null) { return true; } + // Disabled on certain vscode versions due to memory errors in tree-sitter parsers if (disabledLanguages?.has(languageId)) { return false; } - let absolute; - if (path.isAbsolute(language.module)) { - absolute = language.module; - } else { - absolute = path.join( - context.extensionPath, - "parsers", - language.module + ".wasm", - ); - } - - if (!fs.existsSync(absolute)) { - throw Error(`Parser for ${languageId} not found at ${absolute}`); - } - + const absolute = getWasmPath(language.module); const wasm = path.relative(process.cwd(), absolute); + await initParser; + const lang = await treeSitter.Language.load(wasm); const parser = new treeSitter.Parser(); parser.setLanguage(lang); @@ -141,180 +129,145 @@ export function activate(context: vscode.ExtensionContext) { return true; } - async function open(document: vscode.TextDocument) { + function getWasmPath(moduleName: string): string { + const absolute = path.join( + context.extensionPath, + "parsers", + moduleName + ".wasm", + ); + + if (!fs.existsSync(absolute)) { + throw Error(`Parser ${moduleName} not found at ${absolute}`); + } + + return absolute; + } + + /** + * Open a document and parse it, returning the resulting tree + * @param document the document to open + * @returns the resulting tree, or undefined if the language couldn't be loaded + */ + async function openDocument( + document: vscode.TextDocument, + ): Promise { const uriString = document.uri.toString(); - if (uriString in trees) { - return; + let tree = trees.get(uriString); + + // Document is already opened + if (tree != null) { + return tree; } + // Couldn't load language, skip opening document if (!(await loadLanguage(document.languageId))) { - return; + return undefined; } const language = languages[document.languageId]; + if (language?.parser == null) { throw new Error(`No parser for language ${document.languageId}`); } - const t = language.parser?.parse(document.getText()); - if (t == null) { - throw Error(`Failed to parse ${document.uri.toString()}`); + + tree = language.parser?.parse(document.getText()) ?? undefined; + + if (tree == null) { + throw Error(`Failed to parse ${uriString}`); } - trees[uriString] = t; + + trees.set(uriString, tree); + + return tree; } - function openIfLanguageLoaded(document: vscode.TextDocument) { + /** + * Get the parse tree for a given document, parsing it if necessary + * @param document the document to get the tree for + * @returns the parse tree for the document + */ + async function getTree( + document: vscode.TextDocument, + ): Promise { const uriString = document.uri.toString(); - if (uriString in trees) { - return null; - } + let tree = trees.get(uriString); - const language = languages[document.languageId]; - if (language?.parser == null) { - return null; + if (tree != null) { + return tree; } - const t = language.parser.parse(document.getText()); - if (t == null) { - throw Error(`Failed to parse ${document.uri.toString()}`); - } - trees[uriString] = t; - return t; - } + tree = await openDocument(document); - // NOTE: if you make this an async function, it seems to cause edit anomalies - function edit(edit: vscode.TextDocumentChangeEvent) { - const language = languages[edit.document.languageId]; - if (language == null || language.parser == null) { - return; + if (tree != null) { + return tree; } - updateTree(language.parser, edit); - } - function updateTree( - parser: treeSitter.Parser, - edit: vscode.TextDocumentChangeEvent, - ) { - if (edit.contentChanges.length === 0) { - return; - } - const old = trees[edit.document.uri.toString()]; - if (old == null) { - throw new Error(`No existing tree for ${edit.document.uri.toString()}`); - } - for (const e of edit.contentChanges) { - const startIndex = e.rangeOffset; - const oldEndIndex = e.rangeOffset + e.rangeLength; - const newEndIndex = e.rangeOffset + e.text.length; - const startPos = edit.document.positionAt(startIndex); - const oldEndPos = edit.document.positionAt(oldEndIndex); - const newEndPos = edit.document.positionAt(newEndIndex); - const startPosition = asPoint(startPos); - const oldEndPosition = asPoint(oldEndPos); - const newEndPosition = asPoint(newEndPos); - const delta = new Edit({ - startIndex, - oldEndIndex, - newEndIndex, - startPosition, - oldEndPosition, - newEndPosition, - }); - old.edit(delta); - } - const t = parser.parse(edit.document.getText(), old); - if (t == null) { - throw Error(`Failed to parse ${edit.document.uri.toString()}`); + if (document.languageId in languages) { + validateGetLanguage(document.languageId); + throw new LanguageFailedToLoad(document.languageId); } - trees[edit.document.uri.toString()] = t; + + throw new UnsupportedLanguageError(document.languageId); } - function asPoint(pos: vscode.Position): treeSitter.Point { - return { row: pos.line, column: pos.character }; + /** + * Create a tree-sitter query for a given language and query source + * @param languageId the vscode language id of the language to create the query for + * @param source the source of the query + * @returns the created query, or undefined if the language couldn't be loaded + */ + function createQuery( + languageId: string, + source: string, + ): treeSitter.Query | undefined { + const language = languages[languageId]?.parser?.language; + if (language == null) { + validateGetLanguage(languageId); + return undefined; + } + return new treeSitter.Query(language, source); } - function close(document: vscode.TextDocument) { - delete trees[document.uri.toString()]; + // NOTE: if you make this an async function, it seems to cause edit anomalies + function onChange(edit: vscode.TextDocumentChangeEvent) { + const language = languages[edit.document.languageId]; + if (language?.parser != null) { + trees.updateTree(language.parser, edit); + } } - async function colorAllOpen() { + async function openAllVisibleDocuments() { for (const editor of vscode.window.visibleTextEditors) { - await open(editor.document); + await openDocument(editor.document); } } - async function openIfVisible(document: vscode.TextDocument) { - if ( - vscode.window.visibleTextEditors.some( - (editor) => editor.document.uri.toString() === document.uri.toString(), - ) - ) { - await open(document); + async function openDocumentIfVisible(document: vscode.TextDocument) { + if (isDocumentVisible(document)) { + await openDocument(document); } } + function closeDocument(document: vscode.TextDocument) { + trees.delete(document.uri.toString()); + } + context.subscriptions.push( - vscode.window.onDidChangeVisibleTextEditors(colorAllOpen), - ); - context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(edit)); - context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(close)); - context.subscriptions.push( - vscode.workspace.onDidOpenTextDocument(openIfVisible), + vscode.window.onDidChangeVisibleTextEditors(openAllVisibleDocuments), + vscode.workspace.onDidChangeTextDocument(onChange), + vscode.workspace.onDidCloseTextDocument(closeDocument), + vscode.workspace.onDidOpenTextDocument(openDocumentIfVisible), ); - // Don't wait for the initial color, it takes too long to inspect the themes and causes VSCode extension host to hang - void colorAllOpen(); - - function getTreeForUri(uri: vscode.Uri) { - const ret = trees[uri.toString()]; - - if (ret == null) { - const document = vscode.workspace.textDocuments.find( - (textDocument) => textDocument.uri.toString() === uri.toString(), - ); - - if (document == null) { - throw new Error(`Document ${uri.toString()} is not open`); - } - - const ret = openIfLanguageLoaded(document); - - if (ret != null) { - return ret; - } - - const languageId = document.languageId; - - if (languageId in languages) { - validateGetLanguage(document.languageId); - throw new LanguageStillLoadingError(languageId); - } else { - throw new UnsupportedLanguageError(languageId); - } - } - - return ret; - } + // Don't wait for the initial load, it takes too long to inspect the themes and causes VSCode extension host to hang + void openAllVisibleDocuments(); return { loadLanguage, + getTree, + createQuery, - getTree(document: vscode.TextDocument) { - return getTreeForUri(document.uri); - }, - - createQuery( - languageId: string, - source: string, - ): treeSitter.Query | undefined { - const language = languages[languageId]?.parser?.language; - if (language == null) { - validateGetLanguage(languageId); - return undefined; - } - return new treeSitter.Query(language, source); - }, - - getLanguage(): treeSitter.Language | undefined { + getLanguage() { throw new DeprecatedError("getLanguage"); }, getTreeForUri() { @@ -328,8 +281,3 @@ export function activate(context: vscode.ExtensionContext) { }, }; } - -// this method is called when your extension is deactivated -export function deactivate() { - // Empty -} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..db394a1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,6 @@ +import type { Parser } from "web-tree-sitter"; + +export interface Language { + module: string; + parser?: Parser; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..492ee52 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,9 @@ +import type { TextDocument } from "vscode"; +import { window } from "vscode"; + +export function isDocumentVisible(document: TextDocument): boolean { + const uriString = document.uri.toString(); + return window.visibleTextEditors.some( + (editor) => editor.document.uri.toString() === uriString, + ); +} From 751df628068a6a1cdf09eca4874e5d07e43db880 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 15:06:22 +0100 Subject: [PATCH 5/7] Move languages to separate file --- README.md | 2 +- src/disabledLanguages.ts | 25 +++++++ src/extension.ts | 156 +++++++++------------------------------ src/languages.ts | 60 +++++++++++++++ src/types.ts | 6 -- src/utils.ts | 12 +++ 6 files changed, 134 insertions(+), 127 deletions(-) create mode 100644 src/disabledLanguages.ts create mode 100644 src/languages.ts delete mode 100644 src/types.ts diff --git a/README.md b/README.md index c9aed17..743ec19 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ https://code.visualstudio.com/api/references/vscode-api#extensions. It's straightforward to add any [language with a tree-sitter grammar](https://tree-sitter.github.io/tree-sitter/). 1. Add a dependency on the npm package for that language in [tree-sitter-wasms](https://github.com/cursorless-dev/tree-sitter-wasms) -2. Add a language to the dictionary at the top of `./src/extension.ts` +2. Add a language to the map in [`languages.ts`](./src/languages.ts) 3. Add a reference to `onLanguage:yourlang` to the [activationEvents section of package.json](package.json). `yourlang` must be a [VSCode language identifier](https://code.visualstudio.com/docs/languages/identifiers). 4. Run `npm install` and `npm run compile`, then hit `F5` in VSCode, with this project open, to test your changes. 5. Submit a PR! diff --git a/src/disabledLanguages.ts b/src/disabledLanguages.ts new file mode 100644 index 0000000..1760df5 --- /dev/null +++ b/src/disabledLanguages.ts @@ -0,0 +1,25 @@ +import * as vscode from "vscode"; +import * as semver from "semver"; + +/** + * FIXME: On newer vscode versions some Tree sitter parser throws memory errors + * https://github.com/cursorless-dev/cursorless/issues/2879 + * https://github.com/cursorless-dev/vscode-parse-tree/issues/110 + */ + +const isProblemVersion = + semver.lt(vscode.version, "1.107.0") && semver.gte(vscode.version, "1.98.0"); + +const disabledLanguages = new Set(isProblemVersion ? ["latex", "swift"] : []); + +export function isLanguageDisabled(languageId: string): boolean { + return disabledLanguages.has(languageId); +} + +export function throwIfLanguageIsDisabled(languageId: string) { + if (isLanguageDisabled(languageId)) { + throw new Error( + `${languageId} is disabled on vscode versions 1.98.0 through 1.06.3. See https://github.com/cursorless-dev/cursorless/issues/2879`, + ); + } +} diff --git a/src/extension.ts b/src/extension.ts index a54e872..61a645d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,98 +1,33 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as semver from "semver"; -import * as vscode from "vscode"; -import * as treeSitter from "web-tree-sitter"; +import * as path from "node:path"; +import type { + ExtensionContext, + TextDocument, + TextDocumentChangeEvent, +} from "vscode"; +import { window, workspace } from "vscode"; +import type { Tree } from "web-tree-sitter"; +import { Parser, Query, Language as TreeSitterLanguage } from "web-tree-sitter"; +import { + isLanguageDisabled, + throwIfLanguageIsDisabled, +} from "./disabledLanguages"; import { DeprecatedError, LanguageFailedToLoad, UnsupportedLanguageError, } from "./errors"; +import { languages } from "./languages"; import { Trees } from "./Trees"; -import { isDocumentVisible } from "./utils"; -import type { Language } from "./types"; - -/* eslint-disable @typescript-eslint/naming-convention */ - -// Be sure to declare the language in package.json -const languages: Record = { - "java-properties": { module: "tree-sitter-properties" }, - "talon-list": { module: "tree-sitter-talon" }, - agda: { module: "tree-sitter-agda" }, - c: { module: "tree-sitter-c" }, - clojure: { module: "tree-sitter-clojure" }, - cpp: { module: "tree-sitter-cpp" }, - csharp: { module: "tree-sitter-c_sharp" }, - css: { module: "tree-sitter-css" }, - dart: { module: "tree-sitter-dart" }, - elixir: { module: "tree-sitter-elixir" }, - elm: { module: "tree-sitter-elm" }, - gdscript: { module: "tree-sitter-gdscript" }, - gleam: { module: "tree-sitter-gleam" }, - go: { module: "tree-sitter-go" }, - haskell: { module: "tree-sitter-haskell" }, - html: { module: "tree-sitter-html" }, - java: { module: "tree-sitter-java" }, - javascript: { module: "tree-sitter-javascript" }, - javascriptreact: { module: "tree-sitter-javascript" }, - json: { module: "tree-sitter-json" }, - jsonc: { module: "tree-sitter-json" }, - jsonl: { module: "tree-sitter-json" }, - julia: { module: "tree-sitter-julia" }, - kotlin: { module: "tree-sitter-kotlin" }, - latex: { module: "tree-sitter-latex" }, - lua: { module: "tree-sitter-lua" }, - markdown: { module: "tree-sitter-markdown" }, - nix: { module: "tree-sitter-nix" }, - perl: { module: "tree-sitter-perl" }, - php: { module: "tree-sitter-php" }, - properties: { module: "tree-sitter-properties" }, - python: { module: "tree-sitter-python" }, - r: { module: "tree-sitter-r" }, - ruby: { module: "tree-sitter-ruby" }, - rust: { module: "tree-sitter-rust" }, - scala: { module: "tree-sitter-scala" }, - scm: { module: "tree-sitter-query" }, - scss: { module: "tree-sitter-scss" }, - shellscript: { module: "tree-sitter-bash" }, - sparql: { module: "tree-sitter-sparql" }, - starlark: { module: "tree-sitter-python" }, - swift: { module: "tree-sitter-swift" }, - talon: { module: "tree-sitter-talon" }, - terraform: { module: "tree-sitter-hcl" }, - typescript: { module: "tree-sitter-typescript" }, - typescriptreact: { module: "tree-sitter-tsx" }, - xml: { module: "tree-sitter-xml" }, - yaml: { module: "tree-sitter-yaml" }, - zig: { module: "tree-sitter-zig" }, -}; +import { getWasmPath, isDocumentVisible } from "./utils"; // For some reason this crashes if we put it inside activate // Fix: this isn't a field, suppress package member coloring like Go -const initParser = treeSitter.Parser.init(); +const initParser = Parser.init(); -export function activate(context: vscode.ExtensionContext) { +export function activate(context: ExtensionContext) { // Parse of all visible documents const trees = new Trees(); - /** - * FIXME: On newer vscode versions some Tree sitter parser throws memory errors - * https://github.com/cursorless-dev/cursorless/issues/2879 - * https://github.com/cursorless-dev/vscode-parse-tree/issues/110 - */ - const disabledLanguages = - semver.lt(vscode.version, "1.107.0") && semver.gte(vscode.version, "1.98.0") - ? new Set(["latex", "swift"]) - : null; - - const validateGetLanguage = (languageId: string) => { - if (disabledLanguages?.has(languageId)) { - throw new Error( - `${languageId} is disabled on vscode versions 1.98.0 through 1.06.3. See https://github.com/cursorless-dev/cursorless/issues/2879`, - ); - } - }; - /** * Load the parser model for a given language * @param languageId The vscode language id of the language to load @@ -112,45 +47,31 @@ export function activate(context: vscode.ExtensionContext) { } // Disabled on certain vscode versions due to memory errors in tree-sitter parsers - if (disabledLanguages?.has(languageId)) { + if (isLanguageDisabled(languageId)) { return false; } - const absolute = getWasmPath(language.module); + const absolute = getWasmPath(context.extensionPath, language.module); const wasm = path.relative(process.cwd(), absolute); await initParser; - const lang = await treeSitter.Language.load(wasm); - const parser = new treeSitter.Parser(); + const lang = await TreeSitterLanguage.load(wasm); + const parser = new Parser(); parser.setLanguage(lang); language.parser = parser; return true; } - function getWasmPath(moduleName: string): string { - const absolute = path.join( - context.extensionPath, - "parsers", - moduleName + ".wasm", - ); - - if (!fs.existsSync(absolute)) { - throw Error(`Parser ${moduleName} not found at ${absolute}`); - } - - return absolute; - } - /** * Open a document and parse it, returning the resulting tree * @param document the document to open * @returns the resulting tree, or undefined if the language couldn't be loaded */ async function openDocument( - document: vscode.TextDocument, - ): Promise { + document: TextDocument, + ): Promise { const uriString = document.uri.toString(); let tree = trees.get(uriString); @@ -186,9 +107,7 @@ export function activate(context: vscode.ExtensionContext) { * @param document the document to get the tree for * @returns the parse tree for the document */ - async function getTree( - document: vscode.TextDocument, - ): Promise { + async function getTree(document: TextDocument): Promise { const uriString = document.uri.toString(); let tree = trees.get(uriString); @@ -203,7 +122,7 @@ export function activate(context: vscode.ExtensionContext) { } if (document.languageId in languages) { - validateGetLanguage(document.languageId); + throwIfLanguageIsDisabled(document.languageId); throw new LanguageFailedToLoad(document.languageId); } @@ -216,20 +135,17 @@ export function activate(context: vscode.ExtensionContext) { * @param source the source of the query * @returns the created query, or undefined if the language couldn't be loaded */ - function createQuery( - languageId: string, - source: string, - ): treeSitter.Query | undefined { + function createQuery(languageId: string, source: string): Query | undefined { const language = languages[languageId]?.parser?.language; if (language == null) { - validateGetLanguage(languageId); + throwIfLanguageIsDisabled(languageId); return undefined; } - return new treeSitter.Query(language, source); + return new Query(language, source); } // NOTE: if you make this an async function, it seems to cause edit anomalies - function onChange(edit: vscode.TextDocumentChangeEvent) { + function onChange(edit: TextDocumentChangeEvent) { const language = languages[edit.document.languageId]; if (language?.parser != null) { trees.updateTree(language.parser, edit); @@ -237,26 +153,26 @@ export function activate(context: vscode.ExtensionContext) { } async function openAllVisibleDocuments() { - for (const editor of vscode.window.visibleTextEditors) { + for (const editor of window.visibleTextEditors) { await openDocument(editor.document); } } - async function openDocumentIfVisible(document: vscode.TextDocument) { + async function openDocumentIfVisible(document: TextDocument) { if (isDocumentVisible(document)) { await openDocument(document); } } - function closeDocument(document: vscode.TextDocument) { + function closeDocument(document: TextDocument) { trees.delete(document.uri.toString()); } context.subscriptions.push( - vscode.window.onDidChangeVisibleTextEditors(openAllVisibleDocuments), - vscode.workspace.onDidChangeTextDocument(onChange), - vscode.workspace.onDidCloseTextDocument(closeDocument), - vscode.workspace.onDidOpenTextDocument(openDocumentIfVisible), + window.onDidChangeVisibleTextEditors(openAllVisibleDocuments), + workspace.onDidChangeTextDocument(onChange), + workspace.onDidCloseTextDocument(closeDocument), + workspace.onDidOpenTextDocument(openDocumentIfVisible), ); // Don't wait for the initial load, it takes too long to inspect the themes and causes VSCode extension host to hang diff --git a/src/languages.ts b/src/languages.ts new file mode 100644 index 0000000..4d6de15 --- /dev/null +++ b/src/languages.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { Parser } from "web-tree-sitter"; + +// Be sure to declare the language in package.json +export const languages: Record = { + "java-properties": { module: "tree-sitter-properties" }, + "talon-list": { module: "tree-sitter-talon" }, + agda: { module: "tree-sitter-agda" }, + c: { module: "tree-sitter-c" }, + clojure: { module: "tree-sitter-clojure" }, + cpp: { module: "tree-sitter-cpp" }, + csharp: { module: "tree-sitter-c_sharp" }, + css: { module: "tree-sitter-css" }, + dart: { module: "tree-sitter-dart" }, + elixir: { module: "tree-sitter-elixir" }, + elm: { module: "tree-sitter-elm" }, + gdscript: { module: "tree-sitter-gdscript" }, + gleam: { module: "tree-sitter-gleam" }, + go: { module: "tree-sitter-go" }, + haskell: { module: "tree-sitter-haskell" }, + html: { module: "tree-sitter-html" }, + java: { module: "tree-sitter-java" }, + javascript: { module: "tree-sitter-javascript" }, + javascriptreact: { module: "tree-sitter-javascript" }, + json: { module: "tree-sitter-json" }, + jsonc: { module: "tree-sitter-json" }, + jsonl: { module: "tree-sitter-json" }, + julia: { module: "tree-sitter-julia" }, + kotlin: { module: "tree-sitter-kotlin" }, + latex: { module: "tree-sitter-latex" }, + lua: { module: "tree-sitter-lua" }, + markdown: { module: "tree-sitter-markdown" }, + nix: { module: "tree-sitter-nix" }, + perl: { module: "tree-sitter-perl" }, + php: { module: "tree-sitter-php" }, + properties: { module: "tree-sitter-properties" }, + python: { module: "tree-sitter-python" }, + r: { module: "tree-sitter-r" }, + ruby: { module: "tree-sitter-ruby" }, + rust: { module: "tree-sitter-rust" }, + scala: { module: "tree-sitter-scala" }, + scm: { module: "tree-sitter-query" }, + scss: { module: "tree-sitter-scss" }, + shellscript: { module: "tree-sitter-bash" }, + sparql: { module: "tree-sitter-sparql" }, + starlark: { module: "tree-sitter-python" }, + swift: { module: "tree-sitter-swift" }, + talon: { module: "tree-sitter-talon" }, + terraform: { module: "tree-sitter-hcl" }, + typescript: { module: "tree-sitter-typescript" }, + typescriptreact: { module: "tree-sitter-tsx" }, + xml: { module: "tree-sitter-xml" }, + yaml: { module: "tree-sitter-yaml" }, + zig: { module: "tree-sitter-zig" }, +}; + +interface Language { + module: string; + parser?: Parser; +} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index db394a1..0000000 --- a/src/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Parser } from "web-tree-sitter"; - -export interface Language { - module: string; - parser?: Parser; -} diff --git a/src/utils.ts b/src/utils.ts index 492ee52..711d9b8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,5 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; import type { TextDocument } from "vscode"; import { window } from "vscode"; @@ -7,3 +9,13 @@ export function isDocumentVisible(document: TextDocument): boolean { (editor) => editor.document.uri.toString() === uriString, ); } + +export function getWasmPath(extensionPath: string, moduleName: string): string { + const absolute = path.join(extensionPath, "parsers", moduleName + ".wasm"); + + if (!fs.existsSync(absolute)) { + throw Error(`Parser ${moduleName} not found at ${absolute}`); + } + + return absolute; +} From 6a3e299d34189ec01301d57bbca53a1a70475d81 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 15:23:35 +0100 Subject: [PATCH 6/7] re enabled getTreeForUri --- CHANGELOG.md | 1 - src/extension.ts | 52 +++++++++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48b7d77..995da37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ All notable changes to the "vscode-parse-tree" extension will be documented in t Removed deprecated api endpoints: - `getLanguage` - Replaced by `createQuery` -- `getTreeForUri` - Replaced by `getTree` - `getNodeAtLocation` - No direct replacement. Use scm queries and `createQuery` instead. - `registerLanguage` - No replacement. We don't believe anyone is using this. diff --git a/src/extension.ts b/src/extension.ts index 61a645d..f18402f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,6 +3,7 @@ import type { ExtensionContext, TextDocument, TextDocumentChangeEvent, + Uri, } from "vscode"; import { window, workspace } from "vscode"; import type { Tree } from "web-tree-sitter"; @@ -102,19 +103,42 @@ export function activate(context: ExtensionContext) { return tree; } + /** + * Create a tree-sitter query for a given language and query source + * @param languageId the vscode language id of the language to create the query for + * @param source the source of the query + * @returns the created query, or undefined if the language couldn't be loaded + */ + function createQuery(languageId: string, source: string): Query | undefined { + const language = languages[languageId]?.parser?.language; + if (language == null) { + throwIfLanguageIsDisabled(languageId); + return undefined; + } + return new Query(language, source); + } + /** * Get the parse tree for a given document, parsing it if necessary * @param document the document to get the tree for * @returns the parse tree for the document */ - async function getTree(document: TextDocument): Promise { - const uriString = document.uri.toString(); + async function getTreeForUri(uri: Uri): Promise { + const uriString = uri.toString(); let tree = trees.get(uriString); if (tree != null) { return tree; } + const document = workspace.textDocuments.find( + (textDocument) => textDocument.uri.toString() === uri.toString(), + ); + + if (document == null) { + throw new Error(`Document ${uriString} is not open`); + } + tree = await openDocument(document); if (tree != null) { @@ -129,21 +153,6 @@ export function activate(context: ExtensionContext) { throw new UnsupportedLanguageError(document.languageId); } - /** - * Create a tree-sitter query for a given language and query source - * @param languageId the vscode language id of the language to create the query for - * @param source the source of the query - * @returns the created query, or undefined if the language couldn't be loaded - */ - function createQuery(languageId: string, source: string): Query | undefined { - const language = languages[languageId]?.parser?.language; - if (language == null) { - throwIfLanguageIsDisabled(languageId); - return undefined; - } - return new Query(language, source); - } - // NOTE: if you make this an async function, it seems to cause edit anomalies function onChange(edit: TextDocumentChangeEvent) { const language = languages[edit.document.languageId]; @@ -180,15 +189,16 @@ export function activate(context: ExtensionContext) { return { loadLanguage, - getTree, createQuery, + getTreeForUri, + + getTree(document: TextDocument) { + return getTreeForUri(document.uri); + }, getLanguage() { throw new DeprecatedError("getLanguage"); }, - getTreeForUri() { - throw new DeprecatedError("getTreeForUri"); - }, getNodeAtLocation() { throw new DeprecatedError("getNodeAtLocation"); }, From bab97479acec2248fe7168591049df2bb06cb61c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 16:17:36 +0100 Subject: [PATCH 7/7] Make get tree synchronous again --- src/errors.ts | 22 ++++++++++++-- src/extension.ts | 74 ++++++++++++++++++++++++------------------------ src/utils.ts | 29 +++++++++++++++++-- 3 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 687fbe2..2f4c519 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,3 +1,5 @@ +import type { Uri } from "vscode"; + export class UnsupportedLanguageError extends Error { constructor(language: string) { super( @@ -7,10 +9,10 @@ export class UnsupportedLanguageError extends Error { } } -export class LanguageFailedToLoad extends Error { +export class LanguageStillLoadingError extends Error { constructor(language: string) { - super(`Language '${language}' failed to load`); - this.name = "LanguageFailedToLoad"; + super(`Language '${language}' is still loading; please wait and try again`); + this.name = "LanguageStillLoadingError"; } } @@ -20,3 +22,17 @@ export class DeprecatedError extends Error { this.name = "DeprecatedError"; } } + +export class FailedToParseError extends Error { + constructor(uri: Uri) { + super(`Failed to parse document: ${uri.toString()}`); + this.name = "FailedToParseError"; + } +} + +export class DocumentNotOpenError extends Error { + constructor(uri: Uri) { + super(`Document not open: ${uri.toString()}`); + this.name = "DocumentNotOpenError"; + } +} diff --git a/src/extension.ts b/src/extension.ts index f18402f..f0dc4a6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,12 +14,17 @@ import { } from "./disabledLanguages"; import { DeprecatedError, - LanguageFailedToLoad, + LanguageStillLoadingError, UnsupportedLanguageError, } from "./errors"; import { languages } from "./languages"; import { Trees } from "./Trees"; -import { getWasmPath, isDocumentVisible } from "./utils"; +import { + getOpenDocument, + getWasmPath, + isDocumentVisible, + parseDocument, +} from "./utils"; // For some reason this crashes if we put it inside activate // Fix: this isn't a field, suppress package member coloring like Go @@ -92,65 +97,60 @@ export function activate(context: ExtensionContext) { throw new Error(`No parser for language ${document.languageId}`); } - tree = language.parser?.parse(document.getText()) ?? undefined; - - if (tree == null) { - throw Error(`Failed to parse ${uriString}`); - } + tree = parseDocument(language.parser, document); trees.set(uriString, tree); return tree; } - /** - * Create a tree-sitter query for a given language and query source - * @param languageId the vscode language id of the language to create the query for - * @param source the source of the query - * @returns the created query, or undefined if the language couldn't be loaded - */ - function createQuery(languageId: string, source: string): Query | undefined { - const language = languages[languageId]?.parser?.language; - if (language == null) { - throwIfLanguageIsDisabled(languageId); - return undefined; - } - return new Query(language, source); - } - /** * Get the parse tree for a given document, parsing it if necessary * @param document the document to get the tree for * @returns the parse tree for the document */ - async function getTreeForUri(uri: Uri): Promise { + function getTreeForUri(uri: Uri): Tree { const uriString = uri.toString(); let tree = trees.get(uriString); + // Document is already opened if (tree != null) { return tree; } - const document = workspace.textDocuments.find( - (textDocument) => textDocument.uri.toString() === uri.toString(), - ); - - if (document == null) { - throw new Error(`Document ${uriString} is not open`); - } - - tree = await openDocument(document); + const document = getOpenDocument(uri); + const language = languages[document.languageId]; - if (tree != null) { - return tree; + // Language without a parser, e.g. plaintext + if (language == null) { + throw new UnsupportedLanguageError(document.languageId); } - if (document.languageId in languages) { + // Language definition exists, but the parser is not loaded. Could be a race + // condition or a disabled language. + if (language.parser == null) { throwIfLanguageIsDisabled(document.languageId); - throw new LanguageFailedToLoad(document.languageId); + throw new LanguageStillLoadingError(document.languageId); } - throw new UnsupportedLanguageError(document.languageId); + tree = parseDocument(language.parser, document); + trees.set(uriString, tree); + return tree; + } + + /** + * Create a tree-sitter query for a given language and query source + * @param languageId the vscode language id of the language to create the query for + * @param source the source of the query + * @returns the created query, or undefined if the language couldn't be loaded + */ + function createQuery(languageId: string, source: string): Query | undefined { + const language = languages[languageId]?.parser?.language; + if (language == null) { + throwIfLanguageIsDisabled(languageId); + return undefined; + } + return new Query(language, source); } // NOTE: if you make this an async function, it seems to cause edit anomalies diff --git a/src/utils.ts b/src/utils.ts index 711d9b8..b474e4a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,9 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import type { TextDocument } from "vscode"; -import { window } from "vscode"; +import type { TextDocument, Uri } from "vscode"; +import { window, workspace } from "vscode"; +import type { Parser, Tree } from "web-tree-sitter"; +import { DocumentNotOpenError, FailedToParseError } from "./errors"; export function isDocumentVisible(document: TextDocument): boolean { const uriString = document.uri.toString(); @@ -10,6 +12,19 @@ export function isDocumentVisible(document: TextDocument): boolean { ); } +export function getOpenDocument(uri: Uri): TextDocument { + const uriString = uri.toString(); + const document = workspace.textDocuments.find( + (doc) => doc.uri.toString() === uriString, + ); + + if (document == null) { + throw new DocumentNotOpenError(uri); + } + + return document; +} + export function getWasmPath(extensionPath: string, moduleName: string): string { const absolute = path.join(extensionPath, "parsers", moduleName + ".wasm"); @@ -19,3 +34,13 @@ export function getWasmPath(extensionPath: string, moduleName: string): string { return absolute; } + +export function parseDocument(parser: Parser, document: TextDocument): Tree { + const tree = parser.parse(document.getText()); + + if (tree == null) { + throw new FailedToParseError(document.uri); + } + + return tree; +}