-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4bd2bf6
commit 4223b52
Showing
4 changed files
with
297 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import * as vscode from 'vscode'; | ||
// eslint-disable-next-line n/no-unpublished-import | ||
import TreeSitter, {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', | ||
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values | ||
JavaScriptReact = 'javascript', | ||
Python = 'python', | ||
Rust = 'rust', | ||
TypeScript = 'typescript', | ||
TypeScriptReact = 'tsx', | ||
} | ||
/** | ||
* Ensures that Tree Sitter is loaded. | ||
*/ | ||
export declare function ensureLoaded(): Promise<void>; | ||
/** | ||
* Ensures that the specified language is loaded. | ||
*/ | ||
export declare function ensureLoaded(input: HasLanguage): Promise<void>; | ||
/** | ||
* 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<Tree>; | ||
/** | ||
* 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: unknown[]) => Promise<Query>; | ||
export declare function query(language: HasLanguage, source: string): Promise<Query>; | ||
/** | ||
* 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: unknown[]) => 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: { | ||
<T>(document: vscode.TextDocument, k: (tree: Tree) => T | PromiseLike<T>): Promise<T>; | ||
<T_1>( | ||
document: vscode.TextDocument, | ||
options: DocumentTreeOptions | undefined, | ||
k: (tree: Tree) => T_1 | PromiseLike<T_1> | ||
): Promise<T_1>; | ||
}; | ||
/** | ||
* 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: { | ||
<T>(document: vscode.TextDocument, k: (tree: Tree) => T): T; | ||
<T_1>( | ||
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: <T>( | ||
language: HasLanguage, | ||
source: string, | ||
k: (query: Query) => T | PromiseLike<T> | ||
) => Promise<T>; | ||
/** | ||
* 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: <T>( | ||
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< | ||
T, | ||
Args extends { | ||
delete(): void; | ||
}[], | ||
>(...args: [...Args, (...args: Args) => T]): T; | ||
/** | ||
* Returns the built-in {@link Query} for textobjects of the given language, or | ||
* `undefined` if there is no such built-in query. | ||
* | ||
* This function automatically memoizes its results; callers should neither | ||
* cache nor {@link Query.delete delete} the returned query. | ||
* | ||
* @see https://docs.helix-editor.com/guides/textobject.html | ||
*/ | ||
export declare function textObjectQueryFor( | ||
input: HasLanguage | ||
): Promise<Omit<Query, 'delete'> | undefined>; | ||
/** | ||
* 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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import * as vscode from 'vscode'; | ||
import { fromPosition, toPosition, toRange, type SyntaxNode, type Tree, type Cache as TreeCache } from './tree-sitter-api' | ||
import { TreeCursor } from 'web-tree-sitter'; | ||
export type TreeSitter = typeof import('./tree-sitter-api'); | ||
|
||
// TODO: how to import API | ||
let treeSitter: TreeSitter | undefined; | ||
let treeCache: TreeCache | undefined; | ||
|
||
|
||
function smallestSurroundingFilteredChild(filter: (x: SyntaxNode) => boolean, range: vscode.Range, cursor: TreeCursor): SyntaxNode | undefined { | ||
let node = cursor.currentNode | ||
let boundaries = toRange(node); | ||
if (!boundaries.contains(range)) { | ||
return undefined; | ||
} | ||
|
||
if (cursor.gotoFirstChildForPosition(fromPosition(range.start))) { | ||
node = cursor.currentNode; | ||
boundaries = toRange(node); | ||
let match = boundaries.contains(range) && filter(node) | ||
|
||
while (!match) { | ||
cursor.gotoNextSibling(); | ||
node = cursor.currentNode; | ||
if (toPosition(node.startPosition) > range.start) { | ||
break; | ||
} | ||
boundaries = toRange(node); | ||
match = boundaries.contains(range) && filter(node) | ||
} | ||
|
||
if (match) { | ||
const maybeChildNode = smallestSurroundingFilteredChild(filter, range, cursor); | ||
if (maybeChildNode) { | ||
return maybeChildNode; | ||
} else { | ||
return node; | ||
} | ||
} | ||
} | ||
|
||
cursor.gotoParent(); | ||
return undefined; | ||
} | ||
|
||
async function smallestSurroundingNode(filter: (x: SyntaxNode) => boolean, range: vscode.Range): Promise<SyntaxNode | undefined> { | ||
let editor = vscode.window.activeTextEditor; | ||
if(editor && treeSitter && treeCache) { | ||
// TODO: configure timeout?? | ||
let tree = await treeSitter?.documentTree(editor.document, {cache: treeCache, timeoutMs: 2000}); | ||
let node = smallestSurroundingFilteredChild(filter, range, tree.walk()); | ||
if (node) { | ||
return node; | ||
} else { | ||
return tree.rootNode; | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
||
// filters to be used for `smallestSurroundingNode` | ||
function isNamed(x: SyntaxNode) { | ||
return x.isNamed; | ||
} | ||
|
||
function anyNode(x: SyntaxNode) { | ||
return true | ||
} | ||
|
||
// STEPS: | ||
// 1. find node using functions above | ||
// 2. (lazy?) computation of boundary sequence | ||
// 3. do we leverage untiMotions over this boundary sequence? (how do we deal with statefulness of cursors?) | ||
// 3.b - implement expansions separately | ||
|
||
export function registerTreeSitter(context: vscode.ExtensionContext) { | ||
context.subscriptions.push( | ||
vscode.commands.registerCommand('selection-utilities.nextNamedSibling', () => | ||
smallestSurroundingFilteredChild(isNamed, sel) | ||
); | ||
); | ||
|
||
treeSitter = await vscode.extensions.getExtension<TreeSitter>( | ||
"gregoire.tree-sitter", | ||
)?.activate(); | ||
if(treeSitter) { | ||
treeCache = new treeSitter.Cache(); | ||
} | ||
} |