Skip to content

Commit

Permalink
Improve word highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
RedCMD committed Dec 26, 2024
1 parent d3c3757 commit dea70d9
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 59 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,5 @@ Highlights scope names with their own themed colour in realtime:
* Move to LanguageServer
* Add unit tests
* Improve TreeSitter Query performance: [Node contains `&fieldName`](https://github.com/tree-sitter/tree-sitter/issues/3956), [Caching or Serializing a `TSQuery`](https://github.com/tree-sitter/tree-sitter/issues/1942)
* Improve ctrl+click definitions performance
* Add [FlameGraph](https://www.brendangregg.com/flamegraphs.html) [schema](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile)
* Update [CHANGELOG.md](/CHANGELOG.md)
4 changes: 2 additions & 2 deletions language-configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
["[", "]"],
["(", ")"]
],
// "wordPattern" doesn't support `atomic` groups or `possessive quantifiers`, leading to catastrophic backtracking. Workaround is to use a lookahead and backreference /(?=(?<name>)...)\\k<name>/
// "wordPattern" doesn't support `atomic` groups or `possessive quantifiers`, leading to catastrophic backtracking. Workaround is to use a lookahead, named-group and backreference /(?=(?<name>)...)\\k<name>/
// "wordPattern" is used in reference for intellisense suggestion triggering, word highlighting and ctrl+hover definitions
"wordPattern": "(?<=(^|(?<!\\\\)[\\[\\]{}:,\\t])\\s*)(?=(?<json>[\\w/$]+))\\k<json>(?=\\s*($|[\\[\\]{}:,\"\\t](?!\\\\)))|(?<=(?<!\\\\)[\"\\s]|\\b[LR]:)(?=(?<string>[a-zA-Z$/][\\w./$-]*\\w))\\k<string>(?=[\"\\s])|(?<=(?<!\\\\)\")(?!\")(?=(?<include>[\\w.$-]*#?[\\w.#$-]*))\\k<include>(?=\")"
"wordPattern": "(?<=(^|(?<!\\\\)[\\[\\]{}:,\t])\\s*)(?=(?<json>[\\w/$]+))\\k<json>(?=\\s*($|[\\[\\]{}:,\"\t](?!\\\\)))|(?<=(?<!\\\\)[\" (]-?|\\b[LR]:)(?=(?<string>[a-zA-Z./$][\\w./$-]*[\\w.]))\\k<string>(?=[\" )])|(?<=(?<!\\\\)\")(?=(?<include>[\\w.#$-]+))\\k<include>(?=\")"
}
2 changes: 1 addition & 1 deletion src/DiagnosticCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ function diagnosticsDeadTextMateCode(diagnostics: vscode.Diagnostic[], rootNode:
diagnostics.push(diagnostic);
}

// vscode.window.showInformationMessage(`dead ${(performance.now() - start).toFixed(3)}ms`);
// vscode.window.showInformationMessage(`dead ${(performance.now() - start).toFixed(3)}ms\n${JSON.stringify(diagnostics, stringify)}`);
}

async function diagnosticsMismatchingRootScopeName(diagnostics: vscode.Diagnostic[], rootNode: SyntaxNode, document: vscode.TextDocument) {
Expand Down
116 changes: 81 additions & 35 deletions src/Providers/DocumentHighlightProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,30 @@ import { getTrees, queryNode, toPoint, toRange } from "../TreeSitter";

export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {
provideDocumentHighlights(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DocumentHighlight[] | undefined {
// vscode.window.showInformationMessage(JSON.stringify("DocumentHighlights"));
// vscode.window.showInformationMessage(`DocumentHighlights ${JSON.stringify(position)}\n${JSON.stringify(document)}`);
// const start = performance.now();
const trees = getTrees(document);
const jsonTree = trees.jsonTree;
const rootNode = trees.jsonTree.rootNode;
const point = toPoint(position);

const cursorQuery = `;scm
(key) @key
(value !scopeName !ruleName !self !base) @value
(capture . (key) @key)
(repo . (key) @repo)
(json (scopeName (value) @rootScopeName))
(include (value (scopeName) !ruleName !base) @scopeName)
(include (value (scopeName) @scopeName !ruleName !base))
(include (value (ruleName)) @include)
(include (value !scopeName (self) @self))
(include (value (base) @base))
(name (value (scope) @scope))
(name (value (scope) @name))
(contentName (value (scope) @name))
(injectionSelector (value (scope) @injection))
(injection (key (scope) @injection))
`;
const cursorCapture = queryNode(jsonTree.rootNode, cursorQuery, point);
const fallbackQuery = `;scm
(key) @key
(value !scopeName !ruleName !self !base) @value
`;
const cursorCapture = queryNode(rootNode, cursorQuery, point) || queryNode(rootNode, fallbackQuery, point);
// vscode.window.showInformationMessage(JSON.stringify(cursorCapture));
if (!cursorCapture) {
return;
Expand All @@ -34,18 +39,22 @@ export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {


// const scopeName = cursorNode.parent.childForFieldName('scopeName')?.text;
const rootScopeName = queryNode(jsonTree.rootNode, `(json (scopeName (value) @scopeName))`).pop()?.node?.text;
const rootScopeName = queryNode(rootNode, `(json (scopeName (value) @scopeName))`).pop()?.node?.text;


let query = ``;
switch (cursorName) {
case 'key':
const cursorType = cursorNode.parent!.type;
if (cursorType != 'repo') {
query = `(${cursorType} . (key) @key (#eq? @key "${cursorText}"))`;
break;
}
// FallThrough
query = `;scm
(${cursorType} . (key) @key (#eq? @key "${cursorText}"))
`;
break;
case 'value':
query = `;scm
((value) @value (#eq? @value "${cursorText}"))
`;
break;
case 'repo':
query = `;scm
(repo (key) @repo (#eq? @repo "${cursorText}"))
Expand All @@ -57,46 +66,83 @@ export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {
)
`;
break;
case 'value':
query = `(_ (value) @value (#eq? @value "${cursorText}"))`;
break;
case 'self':
case 'rootScopeName':
query = `(json (scopeName (value) @scopeName))`;
query += `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${rootScopeName}") !ruleName !base) @include)`;
query += `(include (value (self) !scopeName) @self)`;
query = `;scm
(json (scopeName (value) @scopeName))
(include (value (scopeName) @scopeName (#eq? @scopeName "${rootScopeName}") !base))
(include (value (self) !scopeName) @self)
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
`;
break;
case 'base':
query = `(include (value (base)) @base)`;
query = `;scm
(include (value (base)) @base)
`;
break;
case 'scopeName':
const scopeName = cursorNode.childForFieldName('scopeName')?.text;
query = `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${scopeName}") !ruleName !base) @include)`;
if (scopeName == rootScopeName) {
query += `(json (scopeName (value) @scopeName))`;
query += `(include (value (self) !scopeName) @self)`;
query = `;scm
(include (value (scopeName) @scopeName (#eq? @scopeName "${cursorText}") !base))
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
`;
if (cursorText == rootScopeName) {
query += `;scm
(json (scopeName (value) @scopeName))
(include (value (self) !scopeName) @self)
`;
}
break;
case 'include':
const scopeName2 = cursorNode.childForFieldName('scopeName')?.text;
const ruleName = cursorNode.childForFieldName('ruleName')?.text;
if (!scopeName2 || scopeName2 == rootScopeName) {
query = `(include (value (scopeName)? @_scopeName (#eq? @_scopeName "${scopeName2 ?? rootScopeName}") (ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")) @include)`;
query += `(repo (key) @repo (#eq? @repo "${ruleName}"))`;
const scopeName = cursorNode.childForFieldName('scopeName')?.text || '';
const ruleName = cursorNode.childForFieldName('ruleName')?.text || '';
if (!scopeName || scopeName == rootScopeName) {
query = `;scm
(include
(value
(scopeName)? @_scopeName (#eq? @_scopeName "${scopeName || rootScopeName}")
(ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")
) @include
)
(repo (key) @repo (#eq? @repo "${ruleName}"))
`;
}
else {
query = `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${scopeName2}") (ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")) @include)`;
query = `;scm
(include
(value
(scopeName) @_scopeName (#eq? @_scopeName "${scopeName}")
(ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")
) @include
)
`;
}
break;
case 'scope':
query = `(name (value (scope) @scope (#eq? @scope "${cursorText}")))`;
case 'name':
query = `;scm
(name (value (scope) @scope (#eq? @scope "${cursorText}")))
(contentName (value (scope) @scope (#eq? @scope "${cursorText}")))
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
`;
break;
case 'injection':
query = `;scm
(name (value (scope) @scope (#eq? @scope "${cursorText}")))
(contentName (value (scope) @scope (#eq? @scope "${cursorText}")))
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
(include (value (scopeName) @scopeName (#eq? @scopeName "${cursorText}") !base))
(json (scopeName (value) @scopeName (#eq? @scopeName "${cursorText}")))
`;
break;
default:
return;
}

const documentHighlights: vscode.DocumentHighlight[] = [];
const queryCaptures = queryNode(jsonTree.rootNode, query);
const queryCaptures = queryNode(rootNode, query);
for (const queryCapture of queryCaptures) {
if (queryCapture.name.charAt(0) == '_') {
// Ignore internal use captures
Expand All @@ -108,7 +154,7 @@ export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {
documentHighlights.push(documentHighlight);
}

// vscode.window.showInformationMessage(`documentHighlights ${(performance.now() - start).toFixed(3)}ms\n${JSON.stringify(documentHighlights)}`);
// vscode.window.showInformationMessage(`documentHighlights ${(performance.now() - start).toFixed(3)}ms\n${query}\n${JSON.stringify(documentHighlights)}`);
return documentHighlights;
}
};
20 changes: 11 additions & 9 deletions src/TreeSitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,16 @@ export function queryNode(node: Parser.SyntaxNode, queryString: string, startPoi
if (query == null && node) {
const language = node.tree.getLanguage();
// const start = performance.now();
query = language.query(queryString);
// if (performance.now() - start > 100) {
// vscode.window.showInformationMessage(`queryString ${(performance.now() - start).toFixed(3)}ms: ${queryString}\n${JSON.stringify(query)}`);
// }
query.disableCapture('_ignore_');
queryCache[queryString] = query;
// vscode.window.showInformationMessage(JSON.stringify(query, stringify));
// vscode.window.showInformationMessage(JSON.stringify(queryString));
try {
query = language.query(queryString);
// if (performance.now() - start > 100) {
// vscode.window.showInformationMessage(`queryString ${(performance.now() - start).toFixed(3)}ms: ${queryString}\n${JSON.stringify(query)}`);
// }
query.disableCapture('_ignore_');
queryCache[queryString] = query;
// vscode.window.showInformationMessage(JSON.stringify(query, stringify));
// vscode.window.showInformationMessage(JSON.stringify(queryString));
} catch (error) { }
}

// vscode.window.showInformationMessage(performance.now() - start + "ms");
Expand All @@ -115,7 +117,7 @@ export function queryNode(node: Parser.SyntaxNode, queryString: string, startPoi

// const queryCaptures = query.captures(node, queryOptions);
// const start = performance.now();
const queryMatches = node ? query.matches(node, queryOptions) : [];
const queryMatches = node && query ? query.matches(node, queryOptions) : [];
// if (query.didExceedMatchLimit()) {
// vscode.window.showInformationMessage(`matchLimit ${queryString}\n${JSON.stringify(queryMatches)}`);
// }
Expand Down
86 changes: 76 additions & 10 deletions src/tree-sitter/tree-sitter-json/grammar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/section-3-creating-parsers.md
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/src/creating-parsers/3-writing-the-grammar.md
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

Expand Down Expand Up @@ -262,7 +262,7 @@ module.exports = grammar({
choice(
repeat1(
choice(
$._scope,
$._name_scope,
/ +/,
),
),
Expand All @@ -271,7 +271,7 @@ module.exports = grammar({
$.value,
),
),
_scope: $ => fieldAlias($,
_name_scope: $ => fieldAlias($,
"scope",
prec.right(
repeat1(
Expand Down Expand Up @@ -309,14 +309,22 @@ module.exports = grammar({
),
_injectionSelectorValue: $ => choice(
array($, $._injectionSelectorValue),
string($),
string($,
alias(
choice(
$.injection_string,
$._forceStringNode,
),
$.value,
),
),
),
injections: $ => pair($,
"injections",
object($, $.injection),
),
injection: $ => pair($,
undefined,
$.injection_string,
object($,
choice(
$.patterns,
Expand All @@ -325,6 +333,62 @@ module.exports = grammar({
),
),
),
injection_string: $ => choice(
repeat1($._injection_scopes),
seq(
seq(
optional($._injection_whitespace),
choice(
alias(
token(
prec(1,
choice(
'L:',
'R:',
),
),
),
$.selector,
),
seq(
alias(
/[_a-zA-Z0-9:.]:/,
$.selector,
),
$._injection_whitespace,
),
),
),
repeat($._injection_scopes),
),
),
_injection_scopes: $ => choice(
// fieldAlias($,
// "scope",
// /[_a-zA-Z0-9.:][_a-zA-Z0-9.:-]*/,
// ),
alias(
/[_a-zA-Z0-9.:][_a-zA-Z0-9.:-]*/,
$.scope,
),
'-',
',',
'|',
seq(
'(',
repeat($._injection_scopes),
')',
),
$._injection_whitespace,
),
_injection_whitespace: $ => token(
repeat1(
choice(
/\\[\\"/bfnrt]/,
/[^\\"\w.:|()-]/,
),
),
),

match: $ => pair($,
"match",
Expand Down Expand Up @@ -664,11 +728,13 @@ function pair($, key, value) {
$._string,
$._forceStringNode,
) :
token(
prec.right(-1,
key,
),
),
typeof key == "string" ?
token(
prec.right(-1,
key,
),
) :
key,
),
),
$._colon,
Expand Down
2 changes: 1 addition & 1 deletion src/tree-sitter/tree-sitter-regex/grammar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/section-3-creating-parsers.md
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/src/creating-parsers/3-writing-the-grammar.md
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

Expand Down

0 comments on commit dea70d9

Please sign in to comment.