Skip to content

Commit dea70d9

Browse files
committed
Improve word highlighting
1 parent d3c3757 commit dea70d9

File tree

7 files changed

+172
-59
lines changed

7 files changed

+172
-59
lines changed

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,5 @@ Highlights scope names with their own themed colour in realtime:
8383
* Move to LanguageServer
8484
* Add unit tests
8585
* 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)
86-
* Improve ctrl+click definitions performance
8786
* Add [FlameGraph](https://www.brendangregg.com/flamegraphs.html) [schema](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-Profile)
8887
* Update [CHANGELOG.md](/CHANGELOG.md)

language-configuration.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
["[", "]"],
4848
["(", ")"]
4949
],
50-
// "wordPattern" doesn't support `atomic` groups or `possessive quantifiers`, leading to catastrophic backtracking. Workaround is to use a lookahead and backreference /(?=(?<name>)...)\\k<name>/
50+
// "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>/
5151
// "wordPattern" is used in reference for intellisense suggestion triggering, word highlighting and ctrl+hover definitions
52-
"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>(?=\")"
52+
"wordPattern": "(?<=(^|(?<!\\\\)[\\[\\]{}:,\t])\\s*)(?=(?<json>[\\w/$]+))\\k<json>(?=\\s*($|[\\[\\]{}:,\"\t](?!\\\\)))|(?<=(?<!\\\\)[\" (]-?|\\b[LR]:)(?=(?<string>[a-zA-Z./$][\\w./$-]*[\\w.]))\\k<string>(?=[\" )])|(?<=(?<!\\\\)\")(?=(?<include>[\\w.#$-]+))\\k<include>(?=\")"
5353
}

src/DiagnosticCollection.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ function diagnosticsDeadTextMateCode(diagnostics: vscode.Diagnostic[], rootNode:
546546
diagnostics.push(diagnostic);
547547
}
548548

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

552552
async function diagnosticsMismatchingRootScopeName(diagnostics: vscode.Diagnostic[], rootNode: SyntaxNode, document: vscode.TextDocument) {

src/Providers/DocumentHighlightProvider.ts

+81-35
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,30 @@ import { getTrees, queryNode, toPoint, toRange } from "../TreeSitter";
33

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

1212
const cursorQuery = `;scm
13-
(key) @key
14-
(value !scopeName !ruleName !self !base) @value
1513
(capture . (key) @key)
1614
(repo . (key) @repo)
1715
(json (scopeName (value) @rootScopeName))
18-
(include (value (scopeName) !ruleName !base) @scopeName)
16+
(include (value (scopeName) @scopeName !ruleName !base))
1917
(include (value (ruleName)) @include)
2018
(include (value !scopeName (self) @self))
2119
(include (value (base) @base))
22-
(name (value (scope) @scope))
20+
(name (value (scope) @name))
21+
(contentName (value (scope) @name))
22+
(injectionSelector (value (scope) @injection))
23+
(injection (key (scope) @injection))
2324
`;
24-
const cursorCapture = queryNode(jsonTree.rootNode, cursorQuery, point);
25+
const fallbackQuery = `;scm
26+
(key) @key
27+
(value !scopeName !ruleName !self !base) @value
28+
`;
29+
const cursorCapture = queryNode(rootNode, cursorQuery, point) || queryNode(rootNode, fallbackQuery, point);
2530
// vscode.window.showInformationMessage(JSON.stringify(cursorCapture));
2631
if (!cursorCapture) {
2732
return;
@@ -34,18 +39,22 @@ export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {
3439

3540

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

3944

4045
let query = ``;
4146
switch (cursorName) {
4247
case 'key':
4348
const cursorType = cursorNode.parent!.type;
44-
if (cursorType != 'repo') {
45-
query = `(${cursorType} . (key) @key (#eq? @key "${cursorText}"))`;
46-
break;
47-
}
48-
// FallThrough
49+
query = `;scm
50+
(${cursorType} . (key) @key (#eq? @key "${cursorText}"))
51+
`;
52+
break;
53+
case 'value':
54+
query = `;scm
55+
((value) @value (#eq? @value "${cursorText}"))
56+
`;
57+
break;
4958
case 'repo':
5059
query = `;scm
5160
(repo (key) @repo (#eq? @repo "${cursorText}"))
@@ -57,46 +66,83 @@ export const DocumentHighlightProvider: vscode.DocumentHighlightProvider = {
5766
)
5867
`;
5968
break;
60-
case 'value':
61-
query = `(_ (value) @value (#eq? @value "${cursorText}"))`;
62-
break;
6369
case 'self':
6470
case 'rootScopeName':
65-
query = `(json (scopeName (value) @scopeName))`;
66-
query += `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${rootScopeName}") !ruleName !base) @include)`;
67-
query += `(include (value (self) !scopeName) @self)`;
71+
query = `;scm
72+
(json (scopeName (value) @scopeName))
73+
(include (value (scopeName) @scopeName (#eq? @scopeName "${rootScopeName}") !base))
74+
(include (value (self) !scopeName) @self)
75+
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
76+
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
77+
`;
6878
break;
6979
case 'base':
70-
query = `(include (value (base)) @base)`;
80+
query = `;scm
81+
(include (value (base)) @base)
82+
`;
7183
break;
7284
case 'scopeName':
73-
const scopeName = cursorNode.childForFieldName('scopeName')?.text;
74-
query = `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${scopeName}") !ruleName !base) @include)`;
75-
if (scopeName == rootScopeName) {
76-
query += `(json (scopeName (value) @scopeName))`;
77-
query += `(include (value (self) !scopeName) @self)`;
85+
query = `;scm
86+
(include (value (scopeName) @scopeName (#eq? @scopeName "${cursorText}") !base))
87+
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
88+
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
89+
`;
90+
if (cursorText == rootScopeName) {
91+
query += `;scm
92+
(json (scopeName (value) @scopeName))
93+
(include (value (self) !scopeName) @self)
94+
`;
7895
}
7996
break;
8097
case 'include':
81-
const scopeName2 = cursorNode.childForFieldName('scopeName')?.text;
82-
const ruleName = cursorNode.childForFieldName('ruleName')?.text;
83-
if (!scopeName2 || scopeName2 == rootScopeName) {
84-
query = `(include (value (scopeName)? @_scopeName (#eq? @_scopeName "${scopeName2 ?? rootScopeName}") (ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")) @include)`;
85-
query += `(repo (key) @repo (#eq? @repo "${ruleName}"))`;
98+
const scopeName = cursorNode.childForFieldName('scopeName')?.text || '';
99+
const ruleName = cursorNode.childForFieldName('ruleName')?.text || '';
100+
if (!scopeName || scopeName == rootScopeName) {
101+
query = `;scm
102+
(include
103+
(value
104+
(scopeName)? @_scopeName (#eq? @_scopeName "${scopeName || rootScopeName}")
105+
(ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")
106+
) @include
107+
)
108+
(repo (key) @repo (#eq? @repo "${ruleName}"))
109+
`;
86110
}
87111
else {
88-
query = `(include (value (scopeName) @_scopeName (#eq? @_scopeName "${scopeName2}") (ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")) @include)`;
112+
query = `;scm
113+
(include
114+
(value
115+
(scopeName) @_scopeName (#eq? @_scopeName "${scopeName}")
116+
(ruleName) @_ruleName (#eq? @_ruleName "${ruleName}")
117+
) @include
118+
)
119+
`;
89120
}
90121
break;
91-
case 'scope':
92-
query = `(name (value (scope) @scope (#eq? @scope "${cursorText}")))`;
122+
case 'name':
123+
query = `;scm
124+
(name (value (scope) @scope (#eq? @scope "${cursorText}")))
125+
(contentName (value (scope) @scope (#eq? @scope "${cursorText}")))
126+
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
127+
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
128+
`;
129+
break;
130+
case 'injection':
131+
query = `;scm
132+
(name (value (scope) @scope (#eq? @scope "${cursorText}")))
133+
(contentName (value (scope) @scope (#eq? @scope "${cursorText}")))
134+
(injectionSelector (value (scope) @scope (#eq? @scope "${cursorText}")))
135+
(injection (key (scope) @scope (#eq? @scope "${cursorText}")))
136+
(include (value (scopeName) @scopeName (#eq? @scopeName "${cursorText}") !base))
137+
(json (scopeName (value) @scopeName (#eq? @scopeName "${cursorText}")))
138+
`;
93139
break;
94140
default:
95141
return;
96142
}
97143

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

111-
// vscode.window.showInformationMessage(`documentHighlights ${(performance.now() - start).toFixed(3)}ms\n${JSON.stringify(documentHighlights)}`);
157+
// vscode.window.showInformationMessage(`documentHighlights ${(performance.now() - start).toFixed(3)}ms\n${query}\n${JSON.stringify(documentHighlights)}`);
112158
return documentHighlights;
113159
}
114160
};

src/TreeSitter.ts

+11-9
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,16 @@ export function queryNode(node: Parser.SyntaxNode, queryString: string, startPoi
9393
if (query == null && node) {
9494
const language = node.tree.getLanguage();
9595
// const start = performance.now();
96-
query = language.query(queryString);
97-
// if (performance.now() - start > 100) {
98-
// vscode.window.showInformationMessage(`queryString ${(performance.now() - start).toFixed(3)}ms: ${queryString}\n${JSON.stringify(query)}`);
99-
// }
100-
query.disableCapture('_ignore_');
101-
queryCache[queryString] = query;
102-
// vscode.window.showInformationMessage(JSON.stringify(query, stringify));
103-
// vscode.window.showInformationMessage(JSON.stringify(queryString));
96+
try {
97+
query = language.query(queryString);
98+
// if (performance.now() - start > 100) {
99+
// vscode.window.showInformationMessage(`queryString ${(performance.now() - start).toFixed(3)}ms: ${queryString}\n${JSON.stringify(query)}`);
100+
// }
101+
query.disableCapture('_ignore_');
102+
queryCache[queryString] = query;
103+
// vscode.window.showInformationMessage(JSON.stringify(query, stringify));
104+
// vscode.window.showInformationMessage(JSON.stringify(queryString));
105+
} catch (error) { }
104106
}
105107

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

116118
// const queryCaptures = query.captures(node, queryOptions);
117119
// const start = performance.now();
118-
const queryMatches = node ? query.matches(node, queryOptions) : [];
120+
const queryMatches = node && query ? query.matches(node, queryOptions) : [];
119121
// if (query.didExceedMatchLimit()) {
120122
// vscode.window.showInformationMessage(`matchLimit ${queryString}\n${JSON.stringify(queryMatches)}`);
121123
// }

src/tree-sitter/tree-sitter-json/grammar.js

+76-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/section-3-creating-parsers.md
1+
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/src/creating-parsers/3-writing-the-grammar.md
22
/// <reference types="tree-sitter-cli/dsl" />
33
// @ts-check
44

@@ -262,7 +262,7 @@ module.exports = grammar({
262262
choice(
263263
repeat1(
264264
choice(
265-
$._scope,
265+
$._name_scope,
266266
/ +/,
267267
),
268268
),
@@ -271,7 +271,7 @@ module.exports = grammar({
271271
$.value,
272272
),
273273
),
274-
_scope: $ => fieldAlias($,
274+
_name_scope: $ => fieldAlias($,
275275
"scope",
276276
prec.right(
277277
repeat1(
@@ -309,14 +309,22 @@ module.exports = grammar({
309309
),
310310
_injectionSelectorValue: $ => choice(
311311
array($, $._injectionSelectorValue),
312-
string($),
312+
string($,
313+
alias(
314+
choice(
315+
$.injection_string,
316+
$._forceStringNode,
317+
),
318+
$.value,
319+
),
320+
),
313321
),
314322
injections: $ => pair($,
315323
"injections",
316324
object($, $.injection),
317325
),
318326
injection: $ => pair($,
319-
undefined,
327+
$.injection_string,
320328
object($,
321329
choice(
322330
$.patterns,
@@ -325,6 +333,62 @@ module.exports = grammar({
325333
),
326334
),
327335
),
336+
injection_string: $ => choice(
337+
repeat1($._injection_scopes),
338+
seq(
339+
seq(
340+
optional($._injection_whitespace),
341+
choice(
342+
alias(
343+
token(
344+
prec(1,
345+
choice(
346+
'L:',
347+
'R:',
348+
),
349+
),
350+
),
351+
$.selector,
352+
),
353+
seq(
354+
alias(
355+
/[_a-zA-Z0-9:.]:/,
356+
$.selector,
357+
),
358+
$._injection_whitespace,
359+
),
360+
),
361+
),
362+
repeat($._injection_scopes),
363+
),
364+
),
365+
_injection_scopes: $ => choice(
366+
// fieldAlias($,
367+
// "scope",
368+
// /[_a-zA-Z0-9.:][_a-zA-Z0-9.:-]*/,
369+
// ),
370+
alias(
371+
/[_a-zA-Z0-9.:][_a-zA-Z0-9.:-]*/,
372+
$.scope,
373+
),
374+
'-',
375+
',',
376+
'|',
377+
seq(
378+
'(',
379+
repeat($._injection_scopes),
380+
')',
381+
),
382+
$._injection_whitespace,
383+
),
384+
_injection_whitespace: $ => token(
385+
repeat1(
386+
choice(
387+
/\\[\\"/bfnrt]/,
388+
/[^\\"\w.:|()-]/,
389+
),
390+
),
391+
),
328392

329393
match: $ => pair($,
330394
"match",
@@ -664,11 +728,13 @@ function pair($, key, value) {
664728
$._string,
665729
$._forceStringNode,
666730
) :
667-
token(
668-
prec.right(-1,
669-
key,
670-
),
671-
),
731+
typeof key == "string" ?
732+
token(
733+
prec.right(-1,
734+
key,
735+
),
736+
) :
737+
key,
672738
),
673739
),
674740
$._colon,

src/tree-sitter/tree-sitter-regex/grammar.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/section-3-creating-parsers.md
1+
// https://github.com/tree-sitter/tree-sitter/blob/master/docs/src/creating-parsers/3-writing-the-grammar.md
22
/// <reference types="tree-sitter-cli/dsl" />
33
// @ts-check
44

0 commit comments

Comments
 (0)