Skip to content

Commit 6395168

Browse files
committed
InlayHintProvider performance improvements
1 parent 22eaf4b commit 6395168

File tree

6 files changed

+113
-101
lines changed

6 files changed

+113
-101
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<!-- - Switched to [recommended](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions) _`major.EVEN_NUMBER.patch` for release versions and `major.ODD_NUMBER.patch` for pre-release versions_ -->
22

3-
## 5.6.3 (preview, unreleased)
3+
## 5.6.3
44

55
- Performance improvements
6-
- Inlay Hints (experimental, work in progress)
6+
- Inlay Hints (experimental)
77

88
## 5.6.2 (preview)
99

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"publisher": "theNestruo",
33
"name": "z80-asm-meter",
44
"version": "5.6.3",
5-
"preview": true,
65
"engines": {
76
"vscode": "^1.93.0"
87
},

src/config.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ class Configuration {
2222
return configurationReader.read("expandSelectionToLine");
2323
}
2424

25-
get debug(): boolean {
26-
27-
return configurationReader.read("debug");
28-
}
29-
3025
// Status bar
3126
readonly statusBar = new StatusBarConfiguration();
3227

src/vscode/InlayHintsProvider.ts

Lines changed: 109 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,9 @@ export class InlayHintsProvider implements vscode.InlayHintsProvider {
7575
return undefined;
7676
}
7777

78-
// Locates the inlay hints, and provides the inlay hints within the requested range
79-
return this.locateInlayHintCandidates(document)
80-
.filter(candidate => range.intersection(candidate.range))
81-
.map(candidate => candidate.provide());
78+
// Locates the inlay hints candidates within the requested range and provides the inlay hints
79+
return this.locateInlayHintCandidates(document, range)
80+
.map(candidate => candidate.provide());
8281
}
8382

8483
resolveInlayHint(hint: vscode.InlayHint, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.InlayHint> {
@@ -89,81 +88,83 @@ export class InlayHintsProvider implements vscode.InlayHintsProvider {
8988
: undefined;
9089
}
9190

92-
private locateInlayHintCandidates(document: vscode.TextDocument): InlayHintCandidate[] {
91+
private locateInlayHintCandidates(document: vscode.TextDocument, range: vscode.Range): InlayHintCandidate[] {
9392

93+
// (for performance reasons)
9494
const lineSeparatorCharacter = config.syntax.lineSeparatorCharacter;
95+
const unlabelledSubroutines = config.inlayHints.unlabelledSubroutines;
96+
const fallthroughSubroutines = config.inlayHints.fallthroughSubroutines;
9597

9698
const candidates: InlayHintCandidate[] = [];
9799

98100
let ongoingCandidates: OngoingInlayHintCandidate[] = [];
99-
let containsCode: boolean = false;
101+
let didContainCode: boolean = false;
100102
for (let i = 0, n = document.lineCount; i < n; i++) {
101103
const line = document.lineAt(i);
104+
105+
// Stops looking for candidates after the range
106+
const isAfterRange = line.range.start.isAfter(range.end);
107+
if (isAfterRange && !ongoingCandidates.length) {
108+
break;
109+
}
110+
102111
const sourceCodes = lineToSourceCode(line.text, lineSeparatorCharacter);
103112
if (!sourceCodes.length || !sourceCodes[0]) {
104-
continue; // (ignore empty lines)
113+
continue; // (ignore empty line)
105114
}
115+
const sourceCode = sourceCodes[0];
106116

107117
// (saves source code on each previously found candidate)
108118
ongoingCandidates.forEach(candidateBuilder => {
109119
candidateBuilder.sourceCode.push(...sourceCodes);
110120
});
111121

112-
// Checks labels (for subroutine starts)
113-
const sourceCode = sourceCodes[0];
114-
if (this.isValidLabel(sourceCode, ongoingCandidates)) {
115-
if (!containsCode) {
122+
// Checks labels for subroutine starts (if not after the range)
123+
if (!isAfterRange && this.isValidLabel(sourceCode, ongoingCandidates)) {
124+
if (!didContainCode) {
116125
// Discards any previous candidates (labels) because they did not contain code
117126
ongoingCandidates = [ new OngoingInlayHintCandidate(line, sourceCodes) ];
118127

119-
} else if (config.inlayHints.fallthroughSubroutines) {
128+
} else if (fallthroughSubroutines) {
120129
// Creates a new candidate on "falls through" labels
121130
ongoingCandidates.push(new OngoingInlayHintCandidate(line, sourceCodes));
122131
}
123132
}
124133

125134
// Checks source code
126135
const metered = mainParser.parseInstruction(sourceCode);
127-
if (!metered) {
128-
// (nothing else to do on unparseable source code)
129-
continue;
130-
}
131-
132-
// Checks code
133-
if (!this.isCode(metered)) {
134-
// Checks data
135-
if (!containsCode && metered.size) {
136-
// Discards previous candidate (label) because it contains data
137-
ongoingCandidates.pop();
138-
}
139-
// (nothing else to do on no-code lines)
140-
continue;
136+
if (!metered || !this.isCode(metered)) {
137+
continue; // (ignore unparseable source code or no-code (data?) lines)
141138
}
142139

143140
// Creates a new candidate on unlabelled code
144-
if (!containsCode && !ongoingCandidates.length && config.inlayHints.unlabelledSubroutines) {
141+
if (!didContainCode && !ongoingCandidates.length && unlabelledSubroutines) {
145142
ongoingCandidates = [ new OngoingInlayHintCandidate(line, sourceCodes) ];
146143
}
147-
containsCode = true;
148144

149-
// Checks subroutine end
145+
didContainCode = true;
146+
147+
const isBeforeRange = line.range.start.isBefore(range.start);
148+
149+
// Checks subroutine ends
150150
if (isUnconditionalJumpOrRetInstruction(metered.instruction)) {
151-
// Ends all incomplete subroutines
152-
candidates.push(...this.withUnconditionalJumpOrRetInstruction(ongoingCandidates, line));
151+
// Ends all incomplete subroutines (if not before the range)
152+
if (!isBeforeRange) {
153+
candidates.push(...this.withUnconditionalJumpOrRetInstruction(ongoingCandidates, line));
154+
}
153155

154-
// (restarts sections)
156+
// (restarts subroutine lookup)
155157
ongoingCandidates = [];
156-
containsCode = false;
158+
didContainCode = false;
157159

158-
// Checks subroutine conditional exit point
159-
} else if (this.isValidConditionalExitPoint(metered.instruction)) {
160-
// Subroutine conditional exit point
160+
// Checks subroutine conditional exit point (if not before the range)
161+
} else if (!isBeforeRange && this.isValidConditionalExitPoint(metered.instruction)) {
161162
candidates.push(...this.withConditionalExitPoint(ongoingCandidates, line));
162163
}
163164
}
164165

165166
// Completes trailing code as subroutine
166-
if (ongoingCandidates.length && containsCode) {
167+
if (ongoingCandidates.length && didContainCode) {
167168
const line = document.lineAt(document.lineCount - 1);
168169
candidates.push(...this.withUnconditionalJumpOrRetInstruction(ongoingCandidates, line));
169170
}
@@ -229,23 +230,37 @@ export class InlayHintsProvider implements vscode.InlayHintsProvider {
229230
}
230231
}
231232

232-
private withUnconditionalJumpOrRetInstruction(ongoingCandidates: OngoingInlayHintCandidate[], endLine: vscode.TextLine): InlayHintCandidate[] {
233+
/**
234+
* Materializes the possible InlayHint candidates
235+
* @returns the InlayHint candidates, before the comment of the first line
236+
*/
237+
private withUnconditionalJumpOrRetInstruction(
238+
ongoingCandidates: OngoingInlayHintCandidate[], endLine: vscode.TextLine): InlayHintCandidate[] {
233239

234-
return ongoingCandidates.map(ongingCandidate =>
235-
ongingCandidate.withUnconditionalJumpOrRetInstruction(endLine));
240+
// (sanity check)
241+
if (!ongoingCandidates.length) {
242+
return [];
243+
}
244+
245+
return ongoingCandidates.map(ongoingCandidate => ongoingCandidate.withUnconditionalJumpOrRetInstruction(endLine));
236246
}
237247

238-
private withConditionalExitPoint(ongoingCandidates: OngoingInlayHintCandidate[], endLine: vscode.TextLine): InlayHintCandidate[] {
248+
/**
249+
* Materializes the possible InlayHint candidate
250+
* @returns the InlayHint candidate, before the comment of the last line
251+
*/
252+
private withConditionalExitPoint(
253+
ongoingCandidates: OngoingInlayHintCandidate[], endLine: vscode.TextLine): InlayHintCandidate[] {
239254

240-
// (sanity check)
255+
// (sanity checks)
241256
if (!ongoingCandidates.length) {
242257
return [];
243258
}
244259

245-
const ongoingCandidate = config.inlayHints.exitPointLabel === "first"
246-
? ongoingCandidates[0]
247-
: ongoingCandidates[ongoingCandidates.length - 1];
248-
return [ ongoingCandidate.withConditionalExitPoint(endLine) ];
260+
return [
261+
ongoingCandidates[config.inlayHints.exitPointLabel === "first" ? 0 : ongoingCandidates.length - 1]
262+
.withConditionalExitPoint(endLine)
263+
];
249264
}
250265
}
251266

@@ -264,7 +279,7 @@ function documentSelector(): readonly vscode.DocumentFilter[] {
264279
*/
265280
class OngoingInlayHintCandidate {
266281

267-
private readonly startLine: vscode.TextLine;
282+
readonly startLine: vscode.TextLine;
268283

269284
readonly sourceCode: SourceCode[];
270285

@@ -274,39 +289,49 @@ class OngoingInlayHintCandidate {
274289
}
275290

276291
/**
277-
* Materializes the possible InlayHint candidate
278-
* @returns the InlayHint candidate, before the comment of the first line
292+
* Materializes the InlayHint candidate
293+
* @returns the InlayHint candidate
279294
*/
280295
withUnconditionalJumpOrRetInstruction(endLine: vscode.TextLine): InlayHintCandidate {
281296

282297
// Computes the InlayHint position before the comment of the first line
283-
const lineCommentPosition = this.sourceCode[0].beforeLineCommentPosition;
284-
const hasLineComment = lineCommentPosition >= 0;
285-
const positionCharacter = hasLineComment
286-
? this.startLine.text.substring(0, lineCommentPosition).trimEnd().length
287-
: this.startLine.text.trimEnd().length;
288-
const inlayHintPosition = this.startLine.range.end.with(undefined, positionCharacter);
289-
290-
return new InlayHintCandidate(inlayHintPosition,
291-
this.startLine, endLine, [...this.sourceCode], hasLineComment);
298+
const [ position, paddingRight ] = this.positionBeforeLineComment(
299+
this.startLine, this.sourceCode[0]);
300+
301+
return new InlayHintCandidate(
302+
position,
303+
paddingRight,
304+
new vscode.Range(this.startLine.range.start, endLine.range.end),
305+
[...this.sourceCode]);
292306
}
293307

294308
/**
295-
* Materializes the possible InlayHint candidate
296-
* @returns the InlayHint candidate, before the comment of the last line
309+
* Materializes the InlayHint candidate
310+
* @returns the InlayHint candidate
297311
*/
298312
withConditionalExitPoint(endLine: vscode.TextLine): InlayHintCandidate {
299313

300314
// Computes the InlayHint position before the comment of the last line
301-
const lineCommentPosition = this.sourceCode[this.sourceCode.length - 1].beforeLineCommentPosition;
315+
const [ position, paddingRight ] = this.positionBeforeLineComment(
316+
endLine, this.sourceCode[this.sourceCode.length - 1]);
317+
318+
return new InlayHintCandidate(
319+
position,
320+
paddingRight,
321+
new vscode.Range(this.startLine.range.start, endLine.range.end),
322+
[...this.sourceCode]);
323+
}
324+
325+
private positionBeforeLineComment(line: vscode.TextLine, sourceCode: SourceCode): [ vscode.Position, boolean ] {
326+
327+
const lineCommentPosition = sourceCode.beforeLineCommentPosition;
302328
const hasLineComment = lineCommentPosition >= 0;
329+
303330
const positionCharacter = hasLineComment
304-
? endLine.text.substring(0, lineCommentPosition).trimEnd().length
305-
: endLine.text.trimEnd().length;
306-
const inlayHintPosition = endLine.range.end.with(undefined, positionCharacter);
331+
? line.text.substring(0, lineCommentPosition).trimEnd().length
332+
: line.text.trimEnd().length;
307333

308-
return new InlayHintCandidate(inlayHintPosition,
309-
this.startLine, endLine, [...this.sourceCode], hasLineComment);
334+
return [ line.range.end.with(undefined, positionCharacter), hasLineComment ];
310335
}
311336
}
312337

@@ -316,17 +341,16 @@ class OngoingInlayHintCandidate {
316341
class InlayHintCandidate {
317342

318343
private readonly position: vscode.Position;
344+
private readonly paddingRight: boolean;
345+
private readonly range: vscode.Range;
319346
private readonly sourceCode: SourceCode[];
320-
readonly range: vscode.Range;
321-
private readonly hasLineComment: boolean;
322347

323-
constructor(position: vscode.Position,
324-
startLine: vscode.TextLine, endLine: vscode.TextLine, sourceCode: SourceCode[], hasLineComment: boolean) {
348+
constructor(position: vscode.Position, paddingRight: boolean, range: vscode.Range, sourceCode: SourceCode[]) {
325349

326350
this.position = position;
351+
this.paddingRight = paddingRight;
352+
this.range = range;
327353
this.sourceCode = sourceCode;
328-
this.range = new vscode.Range(startLine.range.start, endLine.range.end);
329-
this.hasLineComment = hasLineComment;
330354
}
331355

332356
/**
@@ -342,11 +366,10 @@ class InlayHintCandidate {
342366

343367
// Computes the InlayHint label
344368
const label = `${timing}${timingSuffix}`;
345-
const paddingRight = this.hasLineComment; // (only if there are line comments);
346369

347-
return new InlayHint(this.position, label, paddingRight,
370+
return new InlayHint(this.position, this.range, label, this.paddingRight,
348371
totalTimings, totalTiming, timing, timingSuffix,
349-
this.sourceCode[0], this.range);
372+
this.sourceCode[0]);
350373
}
351374
}
352375

@@ -355,27 +378,30 @@ class InlayHintCandidate {
355378
*/
356379
class InlayHint extends vscode.InlayHint {
357380

381+
private readonly range: vscode.Range;
382+
358383
private readonly timing: string;
359384
private readonly timingSuffix: string;
360385
private readonly totalTimings: TotalTimings;
361386
private readonly totalTiming: TotalTimingMeterable;
362-
private readonly sourceCodeForLabel: SourceCode;
363-
private readonly range: vscode.Range;
364387

365-
constructor(position: vscode.Position, label: string, paddingRight: boolean,
388+
private readonly sourceCodeWithLabel: SourceCode;
389+
390+
constructor(position: vscode.Position, range: vscode.Range, label: string, paddingRight: boolean,
366391
totalTimings: TotalTimings, totalTiming: TotalTimingMeterable, timing: string, timingSuffix: string,
367-
sourceCodeForLabel: SourceCode, range: vscode.Range) {
392+
sourceCodeWithLabel: SourceCode) {
368393

369394
super(position, label);
370395
this.paddingLeft = true;
371396
this.paddingRight = paddingRight;
372397

398+
this.range = range;
399+
373400
this.totalTimings = totalTimings;
374401
this.totalTiming = totalTiming;
375402
this.timing = timing;
376403
this.timingSuffix = timingSuffix;
377-
this.sourceCodeForLabel = sourceCodeForLabel;
378-
this.range = range;
404+
this.sourceCodeWithLabel = sourceCodeWithLabel;
379405
}
380406

381407
resolve(): vscode.InlayHint {
@@ -385,13 +411,13 @@ class InlayHint extends vscode.InlayHint {
385411
return this;
386412
}
387413

388-
const label = removeEnd(this.sourceCodeForLabel.label, ":");
414+
const title = removeEnd(this.sourceCodeWithLabel.label, ":");
389415

390416
// Computes the InlayHint tooltip
391417
const timingText = `**${this.timing}**${this.timingSuffix}`;
392418
const rangeText = printRange(this.range);
393419
this.tooltip = new vscode.MarkdownString("", true)
394-
.appendMarkdown(label ? `### ${label}\n\n`: "")
420+
.appendMarkdown(title ? `### ${title}\n\n`: "")
395421
.appendMarkdown(`_${rangeText}_\n\n`)
396422
.appendMarkdown(`${this.totalTiming.statusBarIcon} ${this.totalTiming.name}: ${timingText}\n`)
397423
.appendMarkdown(hrMarkdown)

0 commit comments

Comments
 (0)