From 38001b17bf5b05c2a4b4e67f7eb49c402f061708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Geis?= Date: Sun, 23 Apr 2023 18:21:58 +0900 Subject: [PATCH] commands: experimental/wip support for helix-like keybindings --- .eslintrc.js | 6 +- package.build.ts | 9 ++ package.json | 54 +++++++++++ src/api/data/commands.yaml | 45 +++++++++ src/commands/README.md | 99 +++++++++++-------- src/commands/layouts/azerty.fr.md | 99 +++++++++++-------- src/commands/layouts/qwerty.md | 99 +++++++++++-------- src/commands/load-all.build.ts | 19 +++- src/commands/load-all.ts | 30 ++++++ src/commands/seek.ts | 55 +++++++++++ src/state/extension.ts | 16 ++- src/utils/tree-sitter-api.d.ts | 155 ++++++++++++++++++++++++++++++ src/utils/tree-sitter.ts | 39 ++++++++ yarn.lock | 5 + 14 files changed, 612 insertions(+), 118 deletions(-) create mode 100644 src/utils/tree-sitter-api.d.ts create mode 100644 src/utils/tree-sitter.ts diff --git a/.eslintrc.js b/.eslintrc.js index fa0b036e..6431bac9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,11 @@ module.exports = { ecmaVersion: 2019, sourceType: "module", }, - ignorePatterns: ["out/", "*.js"], + ignorePatterns: [ + "out/", + "*.js", + "tree-sitter-api.d.ts", + ], overrides: [ { files: ["commands/index.ts"], diff --git a/package.build.ts b/package.build.ts index 1a1efef3..18c2f141 100644 --- a/package.build.ts +++ b/package.build.ts @@ -141,6 +141,7 @@ export const pkg = (modules: Builder.ParsedModule[]) => ({ "typescript": "^4.8.4", "unexpected": "^13.0.0", "vsce": "^2.7.0", + "web-tree-sitter": "^0.20.8", "webpack": "^5.72.1", "webpack-cli": "^4.9.2", "yaml": "^2.1.1", @@ -522,6 +523,14 @@ export const pkg = (modules: Builder.ParsedModule[]) => ({ text: "to file whose name is selected", command: "dance.selections.open", }, + "d": { + text: "to definition", + command: "editor.action.revealDefinition", + }, + "r": { + text: "to references", + command: "editor.action.goToReferences", + }, ".": { text: "to last buffer modification position", command: "dance.selections.restore", diff --git a/package.json b/package.json index 2f886958..1bef2d32 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "typescript": "^4.8.4", "unexpected": "^13.0.0", "vsce": "^2.7.0", + "web-tree-sitter": "^0.20.8", "webpack": "^5.72.1", "webpack-cli": "^4.9.2", "yaml": "^2.1.1" @@ -685,6 +686,14 @@ "text": "to file whose name is selected", "command": "dance.selections.open" }, + "d": { + "text": "to definition", + "command": "editor.action.revealDefinition" + }, + "r": { + "text": "to references", + "command": "editor.action.goToReferences" + }, ".": { "text": "to last buffer modification position", "command": "dance.selections.restore", @@ -1394,6 +1403,31 @@ "title": "Select object", "category": "Dance" }, + { + "command": "dance.seek.syntax.child.experimental", + "title": "Select child syntax object", + "category": "Dance" + }, + { + "command": "dance.seek.syntax.experimental", + "title": "Select syntax object", + "category": "Dance" + }, + { + "command": "dance.seek.syntax.next.experimental", + "title": "Select next syntax object", + "category": "Dance" + }, + { + "command": "dance.seek.syntax.parent.experimental", + "title": "Select parent syntax object", + "category": "Dance" + }, + { + "command": "dance.seek.syntax.previous.experimental", + "title": "Select previous syntax object", + "category": "Dance" + }, { "command": "dance.seek.word", "title": "Select to next word start", @@ -2278,6 +2312,26 @@ "command": "dance.seek.object", "when": "dance.mode == 'normal'" }, + { + "command": "dance.seek.syntax.child.experimental", + "when": "dance.mode == 'normal'" + }, + { + "command": "dance.seek.syntax.experimental", + "when": "dance.mode == 'normal'" + }, + { + "command": "dance.seek.syntax.next.experimental", + "when": "dance.mode == 'normal'" + }, + { + "command": "dance.seek.syntax.parent.experimental", + "when": "dance.mode == 'normal'" + }, + { + "command": "dance.seek.syntax.previous.experimental", + "when": "dance.mode == 'normal'" + }, { "command": "dance.seek.word", "when": "dance.mode == 'normal'" diff --git a/src/api/data/commands.yaml b/src/api/data/commands.yaml index a86f485a..22c74f74 100644 --- a/src/api/data/commands.yaml +++ b/src/api/data/commands.yaml @@ -1320,6 +1320,51 @@ seek.object: | Select to inner object end | `askObject.inner.end` | `a-]` (kakoune: normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" }] }]` | | Extend to inner object end | `askObject.inner.end.extend` | `a-}` (kakoune: normal) | `[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]` | +seek.syntax.child.experimental: + title: + en: Select child syntax object + + commands: |- + [".seek.syntax.experimental", { where: "child" }] + +seek.syntax.experimental: + title: + en: Select syntax object + + doc: + en: | + Select syntax object. + + #### Variants + + | Title | Identifier | Command | + | ----------------------------- | ------------------------------ | ------------------------------------------------------ | + | Select next syntax object | `syntax.next.experimental` | `[".seek.syntax.experimental", { where: "next" }]` | + | Select previous syntax object | `syntax.previous.experimental` | `[".seek.syntax.experimental", { where: "previous" }]` | + | Select parent syntax object | `syntax.parent.experimental` | `[".seek.syntax.experimental", { where: "parent" }]` | + | Select child syntax object | `syntax.child.experimental` | `[".seek.syntax.experimental", { where: "child" }]` | + +seek.syntax.next.experimental: + title: + en: Select next syntax object + + commands: |- + [".seek.syntax.experimental", { where: "next" }] + +seek.syntax.parent.experimental: + title: + en: Select parent syntax object + + commands: |- + [".seek.syntax.experimental", { where: "parent" }] + +seek.syntax.previous.experimental: + title: + en: Select previous syntax object + + commands: |- + [".seek.syntax.experimental", { where: "previous" }] + seek.word: title: en: Select to next word start diff --git a/src/commands/README.md b/src/commands/README.md index 87683694..9cebd12a 100644 --- a/src/commands/README.md +++ b/src/commands/README.md @@ -99,42 +99,47 @@ selections are empty search.previous.addAdd previous matchShift+Alt+N (editorTextFocus && dance.mode == 'normal') search.selection.smartSearch current selection (smart)Shift+8 (editorTextFocus && dance.mode == 'normal')NumPad_Multiply (editorTextFocus && dance.mode == 'normal') search.selectionSearch current selectionShift+Alt+8 (editorTextFocus && dance.mode == 'normal')Alt+NumPad_Multiply (editorTextFocus && dance.mode == 'normal') -seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') +seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') seek.leapLeap forward seek.objectSelect object seek.seekSelect to character (excluded)T (editorTextFocus && dance.mode == 'normal') -seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') -seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') -seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') -seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') -seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') -seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') -seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') -seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') -seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') -seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') -seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') -seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') -seek.leap.backwardLeap backward -seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') -seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') -seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') -seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') -seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') +seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') +seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') +seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') +seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') +seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') +seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') +seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') +seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') +seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') +seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') +seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') +seek.leap.backwardLeap backward +seek.syntax.child.experimentalSelect child syntax object +seek.syntax.next.experimentalSelect next syntax object +seek.syntax.parent.experimentalSelect parent syntax object +seek.syntax.previous.experimentalSelect previous syntax object +seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') +seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') +seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') +seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') +seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.syntax.experimentalSelect syntax object seek.wordSelect to next word startW (editorTextFocus && dance.mode == 'normal') selectselect.bufferSelect whole bufferShift+5 (editorTextFocus && dance.mode == 'normal') select.firstVisibleLineSelect to first visible line @@ -997,7 +1002,7 @@ Update selections based on the text surrounding them. -### [`seek.seek`](./seek.ts#L15-L40) +### [`seek.seek`](./seek.ts#L17-L42) Select to character (excluded). @@ -1023,7 +1028,7 @@ Default keybinding: `t` (kakoune: normal) -### [`seek.enclosing`](./seek.ts#L80-L100) +### [`seek.enclosing`](./seek.ts#L82-L102) Select to next enclosing character. @@ -1044,7 +1049,7 @@ Default keybinding: `m` (kakoune: normal) -### [`seek.word`](./seek.ts#L173-L204) +### [`seek.word`](./seek.ts#L175-L206) Select to next word start. @@ -1076,7 +1081,7 @@ Default keybinding: `w` (kakoune: normal) -### [`seek.object`](./seek.ts#L248-L288) +### [`seek.object`](./seek.ts#L250-L290) Select object. @@ -1114,9 +1119,27 @@ This command: - takes an argument `where` of type `"start" | "end"`. - takes an input `input` of type `string`. + + +### [`seek.syntax.experimental`](./seek.ts#L499-L518) + +Select syntax object. + +#### Variants + +| Title | Identifier | Command | +| ----------------------------- | ------------------------------ | ------------------------------------------------------ | +| Select next syntax object | `syntax.next.experimental` | `[".seek.syntax.experimental", { where: "next" }]` | +| Select previous syntax object | `syntax.previous.experimental` | `[".seek.syntax.experimental", { where: "previous" }]` | +| Select parent syntax object | `syntax.parent.experimental` | `[".seek.syntax.experimental", { where: "parent" }]` | +| Select child syntax object | `syntax.child.experimental` | `[".seek.syntax.experimental", { where: "child" }]` | + +This command: +- takes an argument `where` of type `"next" | "previous" | "parent" | "child"`. + -### [`seek.leap`](./seek.ts#L497-L513) +### [`seek.leap`](./seek.ts#L552-L568) Leap forward. diff --git a/src/commands/layouts/azerty.fr.md b/src/commands/layouts/azerty.fr.md index 94755831..00dfec0c 100644 --- a/src/commands/layouts/azerty.fr.md +++ b/src/commands/layouts/azerty.fr.md @@ -84,42 +84,47 @@ selections are empty search.previous.addAdd previous matchShift+Alt+N (editorTextFocus && dance.mode == 'normal') search.selection.smartSearch current selection (smart)Shift+8 (editorTextFocus && dance.mode == 'normal')NumPad_Multiply (editorTextFocus && dance.mode == 'normal') search.selectionSearch current selectionShift+Alt+8 (editorTextFocus && dance.mode == 'normal')Alt+NumPad_Multiply (editorTextFocus && dance.mode == 'normal') -seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') +seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') seek.leapLeap forward seek.objectSelect object seek.seekSelect to character (excluded)T (editorTextFocus && dance.mode == 'normal') -seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') -seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') -seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') -seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') -seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') -seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') -seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') -seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') -seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') -seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') -seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') -seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') -seek.leap.backwardLeap backward -seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') -seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') -seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') -seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') -seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') +seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') +seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') +seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') +seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') +seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') +seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') +seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') +seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') +seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') +seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') +seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') +seek.leap.backwardLeap backward +seek.syntax.child.experimentalSelect child syntax object +seek.syntax.next.experimentalSelect next syntax object +seek.syntax.parent.experimentalSelect parent syntax object +seek.syntax.previous.experimentalSelect previous syntax object +seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') +seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') +seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') +seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') +seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.syntax.experimentalSelect syntax object seek.wordSelect to next word startW (editorTextFocus && dance.mode == 'normal') selectselect.bufferSelect whole bufferShift+5 (editorTextFocus && dance.mode == 'normal') select.firstVisibleLineSelect to first visible line @@ -982,7 +987,7 @@ Update selections based on the text surrounding them. -### [`seek.seek`](../seek.ts#L15-L40) +### [`seek.seek`](../seek.ts#L17-L42) Select to character (excluded). @@ -1008,7 +1013,7 @@ Default keybinding: `t` (kakoune: normal) -### [`seek.enclosing`](../seek.ts#L80-L100) +### [`seek.enclosing`](../seek.ts#L82-L102) Select to next enclosing character. @@ -1029,7 +1034,7 @@ Default keybinding: `m` (kakoune: normal) -### [`seek.word`](../seek.ts#L173-L204) +### [`seek.word`](../seek.ts#L175-L206) Select to next word start. @@ -1061,7 +1066,7 @@ Default keybinding: `w` (kakoune: normal) -### [`seek.object`](../seek.ts#L248-L288) +### [`seek.object`](../seek.ts#L250-L290) Select object. @@ -1099,9 +1104,27 @@ This command: - takes an argument `where` of type `"start" | "end"`. - takes an input `input` of type `string`. + + +### [`seek.syntax.experimental`](../seek.ts#L499-L518) + +Select syntax object. + +#### Variants + +| Title | Identifier | Command | +| ----------------------------- | ------------------------------ | ------------------------------------------------------ | +| Select next syntax object | `syntax.next.experimental` | `[".seek.syntax.experimental", { where: "next" }]` | +| Select previous syntax object | `syntax.previous.experimental` | `[".seek.syntax.experimental", { where: "previous" }]` | +| Select parent syntax object | `syntax.parent.experimental` | `[".seek.syntax.experimental", { where: "parent" }]` | +| Select child syntax object | `syntax.child.experimental` | `[".seek.syntax.experimental", { where: "child" }]` | + +This command: +- takes an argument `where` of type `"next" | "previous" | "parent" | "child"`. + -### [`seek.leap`](../seek.ts#L497-L513) +### [`seek.leap`](../seek.ts#L552-L568) Leap forward. diff --git a/src/commands/layouts/qwerty.md b/src/commands/layouts/qwerty.md index 4662498a..27de64bd 100644 --- a/src/commands/layouts/qwerty.md +++ b/src/commands/layouts/qwerty.md @@ -84,42 +84,47 @@ selections are empty search.previous.addAdd previous matchShift+Alt+N (editorTextFocus && dance.mode == 'normal') search.selection.smartSearch current selection (smart)Shift+8 (editorTextFocus && dance.mode == 'normal')NumPad_Multiply (editorTextFocus && dance.mode == 'normal') search.selectionSearch current selectionShift+Alt+8 (editorTextFocus && dance.mode == 'normal')Alt+NumPad_Multiply (editorTextFocus && dance.mode == 'normal') -seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') +seekseek.enclosingSelect to next enclosing characterM (editorTextFocus && dance.mode == 'normal') seek.leapLeap forward seek.objectSelect object seek.seekSelect to character (excluded)T (editorTextFocus && dance.mode == 'normal') -seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') -seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') -seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') -seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') -seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') -seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') -seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') -seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') -seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') -seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') -seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') -seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') -seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') -seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') -seek.leap.backwardLeap backward -seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') -seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') -seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') -seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') -seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') -seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') -seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.askObjectSelect whole objectAlt+A (editorTextFocus && dance.mode == 'normal')Alt+A (editorTextFocus && dance.mode == 'insert') +seek.askObject.endSelect to whole object end] (editorTextFocus && dance.mode == 'normal') +seek.askObject.end.extendExtend to whole object endShift+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.innerSelect inner objectAlt+I (editorTextFocus && dance.mode == 'normal')Alt+I (editorTextFocus && dance.mode == 'insert') +seek.askObject.inner.endSelect to inner object endAlt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.end.extendExtend to inner object endShift+Alt+] (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.startSelect to inner object startAlt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.inner.start.extendExtend to inner object startShift+Alt+[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.startSelect to whole object start[ (editorTextFocus && dance.mode == 'normal') +seek.askObject.start.extendExtend to whole object startShift+[ (editorTextFocus && dance.mode == 'normal') +seek.backwardSelect to character (excluded, backward)Alt+T (editorTextFocus && dance.mode == 'normal') +seek.enclosing.backwardSelect to previous enclosing characterAlt+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extendExtend to next enclosing characterShift+M (editorTextFocus && dance.mode == 'normal') +seek.enclosing.extend.backwardExtend to previous enclosing characterShift+Alt+M (editorTextFocus && dance.mode == 'normal') +seek.extendExtend to character (excluded)Shift+T (editorTextFocus && dance.mode == 'normal') +seek.extend.backwardExtend to character (excluded, backward)Shift+Alt+T (editorTextFocus && dance.mode == 'normal') +seek.includedSelect to character (included)F (editorTextFocus && dance.mode == 'normal') +seek.included.backwardSelect to character (included, backward)Alt+F (editorTextFocus && dance.mode == 'normal') +seek.included.extendExtend to character (included)Shift+F (editorTextFocus && dance.mode == 'normal') +seek.included.extend.backwardExtend to character (included, backward)Shift+Alt+F (editorTextFocus && dance.mode == 'normal') +seek.leap.backwardLeap backward +seek.syntax.child.experimentalSelect child syntax object +seek.syntax.next.experimentalSelect next syntax object +seek.syntax.parent.experimentalSelect parent syntax object +seek.syntax.previous.experimentalSelect previous syntax object +seek.word.backwardSelect to previous word startB (editorTextFocus && dance.mode == 'normal') +seek.word.extendExtend to next word startShift+W (editorTextFocus && dance.mode == 'normal') +seek.word.extend.backwardExtend to previous word startShift+B (editorTextFocus && dance.mode == 'normal') +seek.word.wsSelect to next non-whitespace word startAlt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.backwardSelect to previous non-whitespace word startAlt+B (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extendExtend to next non-whitespace word startShift+Alt+W (editorTextFocus && dance.mode == 'normal') +seek.word.ws.extend.backwardExtend to previous non-whitespace word startShift+Alt+B (editorTextFocus && dance.mode == 'normal') +seek.wordEndSelect to next word endE (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.extendExtend to next word endShift+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.wsSelect to next non-whitespace word endAlt+E (editorTextFocus && dance.mode == 'normal') +seek.wordEnd.ws.extendExtend to next non-whitespace word endShift+Alt+E (editorTextFocus && dance.mode == 'normal') +seek.syntax.experimentalSelect syntax object seek.wordSelect to next word startW (editorTextFocus && dance.mode == 'normal') selectselect.bufferSelect whole bufferShift+5 (editorTextFocus && dance.mode == 'normal') select.firstVisibleLineSelect to first visible line @@ -982,7 +987,7 @@ Update selections based on the text surrounding them. -### [`seek.seek`](../seek.ts#L15-L40) +### [`seek.seek`](../seek.ts#L17-L42) Select to character (excluded). @@ -1008,7 +1013,7 @@ Default keybinding: `t` (kakoune: normal) -### [`seek.enclosing`](../seek.ts#L80-L100) +### [`seek.enclosing`](../seek.ts#L82-L102) Select to next enclosing character. @@ -1029,7 +1034,7 @@ Default keybinding: `m` (kakoune: normal) -### [`seek.word`](../seek.ts#L173-L204) +### [`seek.word`](../seek.ts#L175-L206) Select to next word start. @@ -1061,7 +1066,7 @@ Default keybinding: `w` (kakoune: normal) -### [`seek.object`](../seek.ts#L248-L288) +### [`seek.object`](../seek.ts#L250-L290) Select object. @@ -1099,9 +1104,27 @@ This command: - takes an argument `where` of type `"start" | "end"`. - takes an input `input` of type `string`. + + +### [`seek.syntax.experimental`](../seek.ts#L499-L518) + +Select syntax object. + +#### Variants + +| Title | Identifier | Command | +| ----------------------------- | ------------------------------ | ------------------------------------------------------ | +| Select next syntax object | `syntax.next.experimental` | `[".seek.syntax.experimental", { where: "next" }]` | +| Select previous syntax object | `syntax.previous.experimental` | `[".seek.syntax.experimental", { where: "previous" }]` | +| Select parent syntax object | `syntax.parent.experimental` | `[".seek.syntax.experimental", { where: "parent" }]` | +| Select child syntax object | `syntax.child.experimental` | `[".seek.syntax.experimental", { where: "child" }]` | + +This command: +- takes an argument `where` of type `"next" | "previous" | "parent" | "child"`. + -### [`seek.leap`](../seek.ts#L497-L513) +### [`seek.leap`](../seek.ts#L552-L568) Leap forward. diff --git a/src/commands/load-all.build.ts b/src/commands/load-all.build.ts index 48435de5..832f5817 100644 --- a/src/commands/load-all.build.ts +++ b/src/commands/load-all.build.ts @@ -89,6 +89,7 @@ export async function build(builder: Builder) { function determineFunctionExpression(f: Builder.ParsedFunction) { const givenParameters: string[] = []; let takeArgument = false; + let takeDocumentTree = false; for (const [name, type] of f.parameters) { let match: RegExpExecArray | null; @@ -133,6 +134,15 @@ function determineFunctionExpression(f: Builder.ParsedFunction) { givenParameters.push("_.extension.registers"); break; + case "treeSitter": + givenParameters.push("_.extension.treeSitterOrThrow()"); + break; + + case "documentTree": + takeDocumentTree = true; + givenParameters.push("documentTree"); + break; + case "count": takeArgument = true; givenParameters.push("getCount(_, argument)"); @@ -189,8 +199,13 @@ function determineFunctionExpression(f: Builder.ParsedFunction) { } } - const inputParameters = ["_", ...(takeArgument ? ["argument"] : [])], - call = `${f.qualifiedName.replace(/\./g, "_")}(${givenParameters.join(", ")})`; + const inputParameters = ["_", ...(takeArgument ? ["argument"] : [])]; + let call = `${f.qualifiedName.replace(/\./g, "_")}(${givenParameters.join(", ")})`; + + if (takeDocumentTree) { + call = + `_.extension.treeSitterOrThrow().withDocumentTree(_.document, (documentTree) => ${call})`; + } return `(${inputParameters.join(", ")}) => _.runAsync((_) => ${call})`; } diff --git a/src/commands/load-all.ts b/src/commands/load-all.ts index 9909f239..fdd4b6ea 100644 --- a/src/commands/load-all.ts +++ b/src/commands/load-all.ts @@ -216,6 +216,7 @@ import { leap as seek_leap, object as seek_object, seek as seek, + syntax_experimental as seek_syntax_experimental, word as seek_word, } from "./seek"; @@ -485,6 +486,11 @@ export const commands: Commands = function () { (_, argument) => _.runAsync((_) => seek_object(_, getInputOr("input", argument), argument["inner"], argument["where"], getShift(argument))), CommandDescriptor.Flags.RequiresActiveEditor, ), + "dance.seek.syntax.experimental": new CommandDescriptor( + "dance.seek.syntax.experimental", + (_, argument) => _.runAsync((_) => _.extension.treeSitterOrThrow().withDocumentTree(_.document, (documentTree) => seek_syntax_experimental(_, _.extension.treeSitterOrThrow(), documentTree, argument["where"]))), + CommandDescriptor.Flags.RequiresActiveEditor, + ), "dance.seek.word": new CommandDescriptor( "dance.seek.word", (_, argument) => _.runAsync((_) => seek_word(_, getRepetitions(_, argument), argument["stopAtEnd"], argument["ws"], getDirection(argument), getShift(argument))), @@ -1058,6 +1064,30 @@ export const commands: Commands = function () { CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay, [[".openMenu", { menu: "object", pass: [{ inner: true, where: "end" , shift: "extend" }] }]], ); + describeAdditionalCommand( + commands, + "dance.seek.syntax.next.experimental", + CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay, + [[".seek.syntax.experimental", { where: "next" }]], + ); + describeAdditionalCommand( + commands, + "dance.seek.syntax.previous.experimental", + CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay, + [[".seek.syntax.experimental", { where: "previous" }]], + ); + describeAdditionalCommand( + commands, + "dance.seek.syntax.parent.experimental", + CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay, + [[".seek.syntax.experimental", { where: "parent" }]], + ); + describeAdditionalCommand( + commands, + "dance.seek.syntax.child.experimental", + CommandDescriptor.Flags.RequiresActiveEditor | CommandDescriptor.Flags.DoNotReplay, + [[".seek.syntax.experimental", { where: "child" }]], + ); describeAdditionalCommand( commands, "dance.seek.leap.backward", diff --git a/src/commands/seek.ts b/src/commands/seek.ts index b21b6c4b..f8a3d6cb 100644 --- a/src/commands/seek.ts +++ b/src/commands/seek.ts @@ -6,6 +6,8 @@ import { CharSet } from "../utils/charset"; import { ArgumentError, assert } from "../utils/errors"; import { escapeForRegExp, execRange } from "../utils/regexp"; import * as TrackedSelection from "../utils/tracked-selection"; +import { TreeSitter } from "../utils/tree-sitter"; +import { SyntaxNode, Tree } from "../utils/tree-sitter-api"; /** * Update selections based on the text surrounding them. @@ -494,6 +496,59 @@ export async function object( throw new Error("unknown object " + JSON.stringify(input)); } +/** + * Select syntax object. + * + * #### Variants + * + * | Title | Identifier | Command | + * | ----------------------------- | ------------------------------ | ------------------------------------------------------ | + * | Select next syntax object | `syntax.next.experimental` | `[".seek.syntax.experimental", { where: "next" }]` | + * | Select previous syntax object | `syntax.previous.experimental` | `[".seek.syntax.experimental", { where: "previous" }]` | + * | Select parent syntax object | `syntax.parent.experimental` | `[".seek.syntax.experimental", { where: "parent" }]` | + * | Select child syntax object | `syntax.child.experimental` | `[".seek.syntax.experimental", { where: "child" }]` | + */ +export function syntax_experimental( + _: Context, + + treeSitter: TreeSitter, + documentTree: Tree, + + where: Argument<"next" | "previous" | "parent" | "child"> = "next", +): void { + const rootNode = documentTree.rootNode; + + Selections.updateByIndex((_, selection) => { + const activeNode = + rootNode.namedDescendantForPosition(treeSitter.fromPosition(selection.active)); + let newNode: SyntaxNode | null; + + switch (where) { + case "next": + newNode = activeNode.nextNamedSibling; + break; + + case "previous": + newNode = activeNode.previousNamedSibling; + break; + + case "child": + newNode = activeNode.firstNamedChild; + break; + + case "parent": + newNode = activeNode.parent; + break; + } + + if (newNode == null) { + return selection; + } + + return Selections.fromRange(treeSitter.toRange(newNode)); + }, _); +} + /** * Leap forward. * diff --git a/src/state/extension.ts b/src/state/extension.ts index c050ad40..c553c928 100644 --- a/src/state/extension.ts +++ b/src/state/extension.ts @@ -4,6 +4,7 @@ import { Editors } from "./editors"; import { Modes } from "./modes"; import { Recorder } from "./recorder"; import { Register, Registers } from "./registers"; +import { RegistersView } from "./registers-view"; import { StatusBar } from "./status-bar"; import { Menu, validateMenu } from "../api"; import type { Commands } from "../commands"; @@ -11,7 +12,7 @@ import { extensionName } from "../utils/constants"; import { AutoDisposable } from "../utils/disposables"; import { assert, CancellationError } from "../utils/errors"; import { SettingsValidator } from "../utils/settings-validator"; -import { RegistersView } from "./registers-view"; +import { onDidLoadTreeSitter, type TreeSitter } from "../utils/tree-sitter"; // =============================================================================================== // == EXTENSION ================================================================================ @@ -37,6 +38,8 @@ export class Extension implements vscode.Disposable { // State. // ========================================================================== + private _treeSitter?: TreeSitter; + /** * `StatusBar` for this instance of the extension. */ @@ -102,6 +105,14 @@ export class Extension implements vscode.Disposable { } } + public treeSitterOrThrow(): TreeSitter { + if (this._treeSitter === undefined) { + throw new Error("TreeSitter is not available"); + } + + return this._treeSitter; + } + public constructor(public readonly commands: Commands) { this.recorder = new Recorder(this); @@ -171,6 +182,9 @@ export class Extension implements vscode.Disposable { // Render views. this._subscriptions.push(new RegistersView(this.registers).register()); + + // Tree Sitter support. + this._subscriptions.push(onDidLoadTreeSitter((treeSitter) => this._treeSitter = treeSitter)); } /** diff --git a/src/utils/tree-sitter-api.d.ts b/src/utils/tree-sitter-api.d.ts new file mode 100644 index 00000000..e7009543 --- /dev/null +++ b/src/utils/tree-sitter-api.d.ts @@ -0,0 +1,155 @@ +import * as vscode from "vscode"; +import * as TreeSitter from "web-tree-sitter"; +import { Query, SyntaxNode, Tree } from "web-tree-sitter"; +export { Query, type SyntaxNode, type Tree, TreeSitter }; + +/** + * A supported language. + */ +export declare enum Language { + C = "c", + Cpp = "cpp", + Go = "go", + Html = "html", + JavaScript = "javascript", + JavaScriptReact = "javascript", + Python = "python", + Rust = "rust", + TypeScript = "typescript", + TypeScriptReact = "tsx" +} +/** + * Ensures that Tree Sitter is loaded. + */ +export declare function ensureLoaded(): Promise; +/** + * Ensures that the specified language is loaded. + */ +export declare function ensureLoaded(input: HasLanguage): Promise; +/** + * Type from which a {@link Language} can be determined. + */ +export type HasLanguage = Language | string | vscode.Uri | vscode.TextDocument; +/** + * Returns the {@link Language} of the file at the given value if it can be + * reliably determined. Otherwise, returns `undefined`. + */ +export declare function determineLanguage(input: HasLanguage): Language | undefined; +/** + * Same as {@link determineLanguage()}, but throws an error on failure instead + * of returning `undefined`. + */ +export declare function determineLanguageOrFail(input: HasLanguage): Language; +/** + * A cache for trees returned by {@link documentTree()} and + * {@link documentTreeSync()}. + */ +export declare class Cache { + constructor(); +} +/** + * Options given to {@link documentTree()} and {@link documentTreeSync()}. + */ +export interface DocumentTreeOptions { + /** + * The language to use; if unspecified, it will be determined using + * {@link determineLanguage()}. + */ + readonly language?: Language; + /** + * The cache used to resolve the tree, or `undefined` if no cache should be + * used. + */ + readonly cache?: Cache; + /** + * The timeout in milliseconds of the operation. + */ + readonly timeoutMs?: number; +} +/** + * Returns the document tree for the specified document, + * {@link ensureLoaded loading} the necessary code first if necessary. + */ +export declare function documentTree(document: vscode.TextDocument, options?: DocumentTreeOptions): Promise; +/** + * Returns the document tree for the specified document, failing if the + * relevant language is not already {@link ensureLoaded loaded}. + */ +export declare function documentTreeSync(document: vscode.TextDocument, options?: DocumentTreeOptions): Tree; +/** + * Compiles the given string into a {@link Query} object which can be used to + * perform queries on nodes of the given language. + * + * @see {@link https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax} + */ +export declare function query(language: HasLanguage): (strings: TemplateStringsArray, ...args: any) => Promise; +export declare function query(language: HasLanguage, source: string): Promise; +/** + * Compiles the given string into a {@link Query} object which can be used to + * perform queries on nodes of the given language, failing if it is not already + * {@link ensureLoaded loaded}. + */ +export declare function querySync(language: HasLanguage): (strings: TemplateStringsArray, ...args: any) => Query; +export declare function querySync(language: HasLanguage, source: string): Query; +/** + * Executes the specified function with the result of {@link documentTree()}, + * {@link Tree.delete() deleting} the tree after the end of the function. + */ +export declare const withDocumentTree: { + (document: vscode.TextDocument, k: (tree: Tree) => T | PromiseLike): Promise; + (document: vscode.TextDocument, options: DocumentTreeOptions | undefined, k: (tree: Tree) => T_1 | PromiseLike): Promise; +}; +/** + * Executes the specified function with the result of {@link documentTreeSync()}, + * {@link Tree.delete() deleting} the tree after the end of the function. + */ +export declare const withDocumentTreeSync: { + (document: vscode.TextDocument, k: (tree: Tree) => T): T; + (document: vscode.TextDocument, options: DocumentTreeOptions | undefined, k: (tree: Tree) => T_1): T_1; +}; +/** + * Executes the specified function with the result of {@link query()}, + * {@link Query.delete() deleting} the query after the end of the function. + */ +export declare const withQuery: (language: HasLanguage, source: string, k: (query: Query) => T | PromiseLike) => Promise; +/** + * Executes the specified function with the result of {@link querySync()}, + * {@link Query.delete() deleting} the query after the end of the function. + */ +export declare const withQuerySync: (language: HasLanguage, source: string, k: (query: Query) => T) => T; +/** + * Executes the specified function with the given arguments, calling + * `arg.delete()` for each `arg` in `args` after the end of its execution. + * + * The function may return a `Promise`, in which case a promise will be + * returned as well. + */ +export declare function using(...args: [...Args, (...args: Args) => T]): T; +/** + * A Tree Sitter point with UTF-16-based offsets. + * + * @see {@link TreeSitter.Point} + */ +export type Point = TreeSitter.Point; +/** + * Converts a Tree Sitter {@link Point} to a {@link vscode.Position}. + */ +export declare function toPosition(point: Point): vscode.Position; +/** + * Converts a {@link vscode.Position} into a Tree Sitter {@link Point}. + */ +export declare function fromPosition(position: vscode.Position): Point; +/** + * Returns the {@link vscode.Position} of a Tree Sitter syntax node. + */ +export declare function toRange(node: SyntaxNode): vscode.Range; +/** + * Returns the start and end Tree Sitter {@link Point} positions of a + * {@link vscode.Range}. + */ +export declare function fromRange(range: vscode.Range): { + startPosition: Point; + endPosition: Point; +}; diff --git a/src/utils/tree-sitter.ts b/src/utils/tree-sitter.ts new file mode 100644 index 00000000..9179d3e1 --- /dev/null +++ b/src/utils/tree-sitter.ts @@ -0,0 +1,39 @@ +import * as vscode from "vscode"; + +const extensionId = "gregoire.tree-sitter"; + +export type TreeSitter = typeof import("./tree-sitter-api"); + +/** + * An event which fires when the [Tree Sitter API]( + * https://github.com/71/vscode-tree-sitter-api) is loaded. + */ +export function onDidLoadTreeSitter(listener: (treeSitter: TreeSitter) => any): vscode.Disposable { + const processExtensions = () => { + const extension = vscode.extensions.getExtension(extensionId); + + if (extension === undefined) { + return; + } + + subscription?.dispose(); + subscription = undefined; + + extension.activate().then((treeSitter) => { + listener(treeSitter); + }); + }; + + let subscription: vscode.Disposable | undefined = vscode.extensions.onDidChange(() => { + processExtensions(); + }); + + processExtensions(); + + return { + dispose(): void { + subscription?.dispose(); + subscription = undefined; + }, + }; +} diff --git a/yarn.lock b/yarn.lock index 37c4d184..6f9a66bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3103,6 +3103,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-tree-sitter@^0.20.8: + version "0.20.8" + resolved "https://registry.yarnpkg.com/web-tree-sitter/-/web-tree-sitter-0.20.8.tgz#1e371cb577584789cadd75cb49c7ddfbc99d04c8" + integrity sha512-weOVgZ3aAARgdnb220GqYuh7+rZU0Ka9k9yfKtGAzEYMa6GgiCzW9JjQRJyCJakvibQW+dfjJdihjInKuuCAUQ== + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"