Skip to content

Commit fa8f4a3

Browse files
committed
Better async support and trigger parse on doc open
1 parent 45d08e0 commit fa8f4a3

File tree

2 files changed

+90
-44
lines changed

2 files changed

+90
-44
lines changed

server/src/project/document.ts

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@ export abstract class BaseProjectDocument {
2626
protected _symbolInformations: SymbolInformation[] = [];
2727
protected _semanticTokens: SemanticTokensManager = new SemanticTokensManager();
2828

29-
isBusy = false;
29+
protected _isBusy = true;
3030
abstract symbolKind: SymbolKind
3131

32+
get Busy() {
33+
return this._isBusy;
34+
}
35+
3236
get activeAttributeElement() {
3337
return this._attributeElements?.at(-1);
3438
}
@@ -63,20 +67,33 @@ export abstract class BaseProjectDocument {
6367
return this._semanticTokens.getSemanticTokens(range);
6468
};
6569

66-
async languageServerSymbolInformationAsync(token: CancellationToken): Promise<SymbolInformation[]> {
67-
while (this.isBusy) {
68-
await sleep(5);
69-
if (token.isCancellationRequested) {
70-
return [];
71-
}
72-
}
70+
languageServerFoldingRanges(): FoldingRange[] {
71+
return this._foldableElements;
72+
}
73+
74+
languageServerSymbolInformation(): SymbolInformation[] {
7375
return this._symbolInformations;
7476
}
7577

78+
languageServerDiagnostics(): PublishDiagnosticsParams {
79+
this._hasDiagnosticElements.forEach(e =>
80+
e.evaluateDiagnostics()
81+
);
82+
return {
83+
uri: this.textDocument.uri,
84+
diagnostics: this._hasDiagnosticElements
85+
.map((e) => e.diagnostics).flat(1) };
86+
}
87+
7688
parseAsync = async (token: CancellationToken): Promise<void> => {
77-
this.isBusy = true;
89+
if (!this._isBusy) {
90+
console.log("Parser busy!");
91+
console.log(`v${this.textDocument.version}: ${this.textDocument.uri}`);
92+
this._isBusy = true;
93+
}
7894
if (await (new SyntaxParser()).parseAsync(this, token)) {
79-
this.isBusy = false;
95+
console.log("Parser idle!");
96+
this._isBusy = false;
8097
}
8198
this._hasDiagnosticElements.forEach(element => {
8299
element.evaluateDiagnostics;
@@ -94,7 +111,6 @@ export abstract class BaseProjectDocument {
94111
}
95112

96113
registerDiagnosticElement(element: HasDiagnosticCapability) {
97-
console.log("Registering diagnostic element");
98114
this._hasDiagnosticElements.push(element);
99115
}
100116

@@ -171,28 +187,6 @@ export abstract class BaseProjectDocument {
171187
this._symbolInformations.push(element.symbolInformation);
172188
return this;
173189
};
174-
175-
/** Get document information */
176-
async getFoldingRanges(token: CancellationToken): Promise<FoldingRange[]> {
177-
while (this.isBusy) {
178-
await sleep(5);
179-
if (token.isCancellationRequested) {
180-
return [];
181-
}
182-
}
183-
this.workspace.connection.console.info('Processing request for Folding Range');
184-
return this._foldableElements;
185-
}
186-
187-
getDiagnostics(): PublishDiagnosticsParams {
188-
this._hasDiagnosticElements.forEach(e =>
189-
e.evaluateDiagnostics()
190-
);
191-
return {
192-
uri: this.textDocument.uri,
193-
diagnostics: this._hasDiagnosticElements
194-
.map((e) => e.diagnostics).flat(1) };
195-
}
196190
}
197191

198192

server/src/project/workspace.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { CancellationToken, CancellationTokenSource, CompletionItem, CompletionParams, DidChangeConfigurationNotification, DidChangeConfigurationParams, DidChangeWatchedFilesParams, DocumentSymbolParams, FoldingRange, FoldingRangeParams, Hover, HoverParams, PublishDiagnosticsParams, SemanticTokensParams, SemanticTokensRangeParams, SymbolInformation, TextDocuments, WorkspaceFoldersChangeEvent, _Connection } from 'vscode-languageserver';
1+
import { CancellationToken, CancellationTokenSource, CompletionItem, CompletionParams, DidChangeConfigurationNotification, DidChangeConfigurationParams, DidChangeWatchedFilesParams, DidOpenTextDocumentParams, DocumentSymbolParams, FoldingRange, FoldingRangeParams, Hover, HoverParams, PublishDiagnosticsParams, SemanticTokensParams, SemanticTokensRangeParams, SymbolInformation, TextDocuments, WorkspaceFoldersChangeEvent, _Connection } from 'vscode-languageserver';
22
import { BaseProjectDocument } from './document';
33
import { LanguageServerConfiguration } from '../server';
44
import { hasConfigurationCapability } from '../capabilities/workspaceFolder';
55
import { TextDocument } from 'vscode-languageserver-textdocument';
6+
import { sleep } from '../utils/helpers';
67

78

89
/**
@@ -62,7 +63,7 @@ class WorkspaceEvents {
6263
private readonly _configuration: LanguageServerConfiguration;
6364
private _parseCancellationToken?: CancellationTokenSource;
6465

65-
activeDocument?: BaseProjectDocument;
66+
private _activeDocument?: BaseProjectDocument;
6667

6768
constructor(params: {connection: _Connection, workspace: Workspace, configuration: LanguageServerConfiguration}) {
6869
this._connection = params.connection;
@@ -74,8 +75,41 @@ class WorkspaceEvents {
7475
this._documents.listen(params.connection);
7576
}
7677

78+
/**
79+
*
80+
* @param version the target document version (zero for any version).
81+
* @param token the cancellation token.
82+
* @returns the document when it is ready or undefined.
83+
*/
84+
private async activeParsedDocument(version: number, token: CancellationToken): Promise<BaseProjectDocument|undefined> {
85+
let document: BaseProjectDocument | undefined;
86+
document = this._activeDocument;
87+
88+
// Sleep between attempting to grab the document.
89+
// Loop while we have undefined or an earlier version.
90+
while (!document || document.textDocument.version < version) {
91+
if (token.isCancellationRequested) {
92+
return;
93+
}
94+
await sleep(5);
95+
document = this._activeDocument;
96+
}
97+
98+
// Return if the version somehow outpaced us.
99+
if (version > 0 && document.textDocument.version != version) {
100+
return;
101+
}
102+
103+
// Return the parsed document.
104+
while (document.Busy) {
105+
await sleep(5);
106+
}
107+
return document;
108+
}
109+
77110
private initialiseConnectionEvents(connection: _Connection) {
78111
connection.onInitialized(() => this._onInitialized());
112+
connection.onDidOpenTextDocument(params => this._onDidOpenTextDocument(params));
79113
connection.onCompletion(params => this._onCompletion(params));
80114
connection.onCompletionResolve(item => this._onCompletionResolve(item));
81115
connection.onDidChangeConfiguration(params => this._onDidChangeConfiguration(params));
@@ -90,11 +124,11 @@ class WorkspaceEvents {
90124
connection.onRequest((method: string, params: object | object[] | any) => {
91125
switch (method) {
92126
case 'textDocument/semanticTokens/full': {
93-
return this.activeDocument?.languageServerSemanticTokens();
127+
return this._activeDocument?.languageServerSemanticTokens();
94128
}
95129
case 'textDocument/semanticTokens/range': {
96130
const rangeParams = params as SemanticTokensRangeParams;
97-
return this.activeDocument?.languageServerSemanticTokens(rangeParams.range);
131+
return this._activeDocument?.languageServerSemanticTokens(rangeParams.range);
98132
}
99133
default:
100134
console.error(`Unresolved request path: ${method}`);
@@ -103,7 +137,7 @@ class WorkspaceEvents {
103137
}
104138

105139
private _sendDiagnostics() {
106-
this._connection.sendDiagnostics(this.activeDocument?.getDiagnostics() ?? {uri: "", diagnostics: []});
140+
this._connection.sendDiagnostics(this._activeDocument?.languageServerDiagnostics() ?? {uri: "", diagnostics: []});
107141
}
108142

109143
private _initialiseDocumentsEvents() {
@@ -134,11 +168,16 @@ class WorkspaceEvents {
134168
}
135169

136170
private async _onDocumentSymbolAsync(params: DocumentSymbolParams, token: CancellationToken): Promise<SymbolInformation[]> {
137-
return await this.activeDocument?.languageServerSymbolInformationAsync(token) ?? [];
171+
const document = await this.activeParsedDocument(0, token);
172+
return document?.languageServerSymbolInformation() ?? [];
138173
}
139174

140175
private async _onFoldingRanges(params: FoldingRangeParams, token: CancellationToken): Promise<FoldingRange[]> {
141-
return await this._workspace.activeDocument?.getFoldingRanges(token) ?? [];
176+
// VSCode is an eager beaver and sends the folding range request before onDidChange or onDidOpen.
177+
await sleep(200);
178+
const document = await this.activeParsedDocument(0, token);
179+
const result = document?.languageServerFoldingRanges();
180+
return result ?? [];
142181
}
143182

144183
private _onHover(params: HoverParams): Hover {
@@ -164,15 +203,28 @@ class WorkspaceEvents {
164203
* This event handler is called whenever a `TextDocuments<TextDocument>` is changed.
165204
* @param doc The document that changed.
166205
*/
167-
async onDidChangeContentAsync(doc: TextDocument) {
206+
async _onDidOpenTextDocument(params: DidOpenTextDocumentParams) {
207+
await this._handleChangeOrOpenAsync(TextDocument.create(
208+
params.textDocument.uri,
209+
params.textDocument.languageId,
210+
params.textDocument.version,
211+
params.textDocument.text
212+
));
213+
}
214+
215+
async onDidChangeContentAsync(document: TextDocument) {
216+
await this._handleChangeOrOpenAsync(document);
168217
// this._parseCancellationToken?.cancel();
169218
// this._parseCancellationToken?.dispose();
219+
}
170220

171-
this.activeDocument = BaseProjectDocument.create(this._workspace, doc);
221+
protected async _handleChangeOrOpenAsync(document: TextDocument) {
222+
this._activeDocument = BaseProjectDocument.create(this._workspace, document);
172223
this._parseCancellationToken = new CancellationTokenSource();
173-
await this.activeDocument.parseAsync(this._parseCancellationToken.token);
224+
await this._activeDocument.parseAsync(this._parseCancellationToken.token);
174225
this._sendDiagnostics();
175226
this._parseCancellationToken = undefined;
176-
this._workspace.activateDocument(this.activeDocument);
227+
this._workspace.activateDocument(this._activeDocument);
177228
}
178229
}
230+

0 commit comments

Comments
 (0)