@@ -53,6 +53,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
5353 filterIncompleteCompletions : true ,
5454 definitionLinkSupport : false
5555 } ;
56+ private deferredRequests : Record < string , [ number , Promise < any > ] > = { } ;
5657
5758 constructor ( private documentsManager : DocumentManager ) { }
5859
@@ -64,14 +65,23 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
6465 this . plugins . push ( plugin ) ;
6566 }
6667
68+ didUpdateDocument ( ) {
69+ this . deferredRequests = { } ;
70+ }
71+
6772 async getDiagnostics ( textDocument : TextDocumentIdentifier ) : Promise < Diagnostic [ ] > {
6873 const document = this . getDocument ( textDocument . uri ) ;
6974 if ( ! document ) {
7075 throw new Error ( 'Cannot call methods on an unopened document' ) ;
7176 }
7277
7378 return flatten (
74- await this . execute < Diagnostic [ ] > ( 'getDiagnostics' , [ document ] , ExecuteMode . Collect )
79+ await this . execute < Diagnostic [ ] > (
80+ 'getDiagnostics' ,
81+ [ document ] ,
82+ ExecuteMode . Collect ,
83+ 'high'
84+ )
7585 ) ;
7686 }
7787
@@ -81,7 +91,12 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
8191 throw new Error ( 'Cannot call methods on an unopened document' ) ;
8292 }
8393
84- return this . execute < Hover > ( 'doHover' , [ document , position ] , ExecuteMode . FirstNonNull ) ;
94+ return this . execute < Hover > (
95+ 'doHover' ,
96+ [ document , position ] ,
97+ ExecuteMode . FirstNonNull ,
98+ 'high'
99+ ) ;
85100 }
86101
87102 async getCompletions (
@@ -99,7 +114,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
99114 await this . execute < CompletionList > (
100115 'getCompletions' ,
101116 [ document , position , completionContext , cancellationToken ] ,
102- ExecuteMode . Collect
117+ ExecuteMode . Collect ,
118+ 'high'
103119 )
104120 ) . filter ( ( completion ) => completion != null ) ;
105121
@@ -141,7 +157,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
141157 const result = await this . execute < CompletionItem > (
142158 'resolveCompletion' ,
143159 [ document , completionItem , cancellationToken ] ,
144- ExecuteMode . FirstNonNull
160+ ExecuteMode . FirstNonNull ,
161+ 'high'
145162 ) ;
146163
147164 return result ?? completionItem ;
@@ -160,7 +177,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
160177 await this . execute < TextEdit [ ] > (
161178 'formatDocument' ,
162179 [ document , options ] ,
163- ExecuteMode . Collect
180+ ExecuteMode . Collect ,
181+ 'high'
164182 )
165183 ) ;
166184 }
@@ -177,7 +195,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
177195 return this . execute < string | null > (
178196 'doTagComplete' ,
179197 [ document , position ] ,
180- ExecuteMode . FirstNonNull
198+ ExecuteMode . FirstNonNull ,
199+ 'high'
181200 ) ;
182201 }
183202
@@ -191,7 +210,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
191210 await this . execute < ColorInformation [ ] > (
192211 'getDocumentColors' ,
193212 [ document ] ,
194- ExecuteMode . Collect
213+ ExecuteMode . Collect ,
214+ 'low'
195215 )
196216 ) ;
197217 }
@@ -210,7 +230,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
210230 await this . execute < ColorPresentation [ ] > (
211231 'getColorPresentations' ,
212232 [ document , range , color ] ,
213- ExecuteMode . Collect
233+ ExecuteMode . Collect ,
234+ 'low'
214235 )
215236 ) ;
216237 }
@@ -228,7 +249,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
228249 await this . execute < SymbolInformation [ ] > (
229250 'getDocumentSymbols' ,
230251 [ document , cancellationToken ] ,
231- ExecuteMode . Collect
252+ ExecuteMode . Collect ,
253+ 'low'
232254 )
233255 ) ;
234256 }
@@ -246,7 +268,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
246268 await this . execute < DefinitionLink [ ] > (
247269 'getDefinitions' ,
248270 [ document , position ] ,
249- ExecuteMode . Collect
271+ ExecuteMode . Collect ,
272+ 'high'
250273 )
251274 ) ;
252275
@@ -274,7 +297,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
274297 await this . execute < CodeAction [ ] > (
275298 'getCodeActions' ,
276299 [ document , range , context , cancellationToken ] ,
277- ExecuteMode . Collect
300+ ExecuteMode . Collect ,
301+ 'high'
278302 )
279303 ) ;
280304 }
@@ -292,15 +316,17 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
292316 return await this . execute < WorkspaceEdit > (
293317 'executeCommand' ,
294318 [ document , command , args ] ,
295- ExecuteMode . FirstNonNull
319+ ExecuteMode . FirstNonNull ,
320+ 'high'
296321 ) ;
297322 }
298323
299324 async updateImports ( fileRename : FileRename ) : Promise < WorkspaceEdit | null > {
300325 return await this . execute < WorkspaceEdit > (
301326 'updateImports' ,
302327 [ fileRename ] ,
303- ExecuteMode . FirstNonNull
328+ ExecuteMode . FirstNonNull ,
329+ 'high'
304330 ) ;
305331 }
306332
@@ -316,7 +342,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
316342 return await this . execute < any > (
317343 'prepareRename' ,
318344 [ document , position ] ,
319- ExecuteMode . FirstNonNull
345+ ExecuteMode . FirstNonNull ,
346+ 'high'
320347 ) ;
321348 }
322349
@@ -333,7 +360,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
333360 return await this . execute < any > (
334361 'rename' ,
335362 [ document , position , newName ] ,
336- ExecuteMode . FirstNonNull
363+ ExecuteMode . FirstNonNull ,
364+ 'high'
337365 ) ;
338366 }
339367
@@ -350,7 +378,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
350378 return await this . execute < any > (
351379 'findReferences' ,
352380 [ document , position , context ] ,
353- ExecuteMode . FirstNonNull
381+ ExecuteMode . FirstNonNull ,
382+ 'high'
354383 ) ;
355384 }
356385
@@ -368,7 +397,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
368397 return await this . execute < any > (
369398 'getSignatureHelp' ,
370399 [ document , position , context , cancellationToken ] ,
371- ExecuteMode . FirstNonNull
400+ ExecuteMode . FirstNonNull ,
401+ 'high'
372402 ) ;
373403 }
374404
@@ -424,7 +454,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
424454 return await this . execute < SemanticTokens > (
425455 'getSemanticTokens' ,
426456 [ document , range , cancellationToken ] ,
427- ExecuteMode . FirstNonNull
457+ ExecuteMode . FirstNonNull ,
458+ 'low'
428459 ) ;
429460 }
430461
@@ -440,7 +471,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
440471 return await this . execute < LinkedEditingRanges > (
441472 'getLinkedEditingRanges' ,
442473 [ document , position ] ,
443- ExecuteMode . FirstNonNull
474+ ExecuteMode . FirstNonNull ,
475+ 'high'
444476 ) ;
445477 }
446478
@@ -463,21 +495,71 @@ export class PluginHost implements LSProvider, OnWatchFileChanges {
463495 private execute < T > (
464496 name : keyof LSProvider ,
465497 args : any [ ] ,
466- mode : ExecuteMode . FirstNonNull
498+ mode : ExecuteMode . FirstNonNull ,
499+ priority : 'low' | 'high'
467500 ) : Promise < T | null > ;
468501 private execute < T > (
469502 name : keyof LSProvider ,
470503 args : any [ ] ,
471- mode : ExecuteMode . Collect
504+ mode : ExecuteMode . Collect ,
505+ priority : 'low' | 'high'
472506 ) : Promise < T [ ] > ;
473- private execute ( name : keyof LSProvider , args : any [ ] , mode : ExecuteMode . None ) : Promise < void > ;
507+ private execute (
508+ name : keyof LSProvider ,
509+ args : any [ ] ,
510+ mode : ExecuteMode . None ,
511+ priority : 'low' | 'high'
512+ ) : Promise < void > ;
474513 private async execute < T > (
475514 name : keyof LSProvider ,
476515 args : any [ ] ,
477- mode : ExecuteMode
516+ mode : ExecuteMode ,
517+ priority : 'low' | 'high'
478518 ) : Promise < ( T | null ) | T [ ] | void > {
479519 const plugins = this . plugins . filter ( ( plugin ) => typeof plugin [ name ] === 'function' ) ;
480520
521+ if ( priority === 'low' ) {
522+ // If a request doesn't have priority, we first wait 1 second to
523+ // 1. let higher priority requests get through first
524+ // 2. wait for possible document changes, which make the request wait again
525+ // Due to waiting, low priority items should preferrably be those who do not
526+ // rely on positions or ranges and rather on the whole document only.
527+ const debounce = async ( ) : Promise < boolean > => {
528+ const id = Math . random ( ) ;
529+ this . deferredRequests [ name ] = [
530+ id ,
531+ new Promise < void > ( ( resolve , reject ) => {
532+ setTimeout ( ( ) => {
533+ if (
534+ ! this . deferredRequests [ name ] ||
535+ this . deferredRequests [ name ] [ 0 ] === id
536+ ) {
537+ resolve ( ) ;
538+ } else {
539+ // We should not get into this case. According to the spec,
540+ // the language client // does not send another request
541+ // of the same type until the previous one is answered.
542+ reject ( ) ;
543+ }
544+ } , 1000 ) ;
545+ } )
546+ ] ;
547+ try {
548+ await this . deferredRequests [ name ] [ 1 ] ;
549+ if ( ! this . deferredRequests [ name ] ) {
550+ return debounce ( ) ;
551+ }
552+ return true ;
553+ } catch ( e ) {
554+ return false ;
555+ }
556+ } ;
557+ const shouldContinue = await debounce ( ) ;
558+ if ( ! shouldContinue ) {
559+ return ;
560+ }
561+ }
562+
481563 switch ( mode ) {
482564 case ExecuteMode . FirstNonNull :
483565 for ( const plugin of plugins ) {
0 commit comments