44 *--------------------------------------------------------------------------------------------*/
55
66import { assertNever } from 'vs/base/common/assert' ;
7+ import { DeferredPromise } from 'vs/base/common/async' ;
78import { CancellationToken } from 'vs/base/common/cancellation' ;
9+ import { SetMap } from 'vs/base/common/collections' ;
810import { onUnexpectedExternalError } from 'vs/base/common/errors' ;
911import { IDisposable } from 'vs/base/common/lifecycle' ;
1012import { ISingleEditOperation } from 'vs/editor/common/core/editOperation' ;
1113import { Position } from 'vs/editor/common/core/position' ;
1214import { Range } from 'vs/editor/common/core/range' ;
1315import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry' ;
14- import { Command , InlineCompletion , InlineCompletionContext , InlineCompletions , InlineCompletionsProvider } from 'vs/editor/common/languages' ;
16+ import { Command , InlineCompletion , InlineCompletionContext , InlineCompletionProviderGroupId , InlineCompletions , InlineCompletionsProvider } from 'vs/editor/common/languages' ;
1517import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry' ;
1618import { ITextModel } from 'vs/editor/common/model' ;
1719import { fixBracketsInLine } from 'vs/editor/common/model/bracketPairsTextModelPart/fixBrackets' ;
@@ -29,17 +31,87 @@ export async function provideInlineCompletions(
2931) : Promise < InlineCompletionProviderResult > {
3032 // Important: Don't use position after the await calls, as the model could have been changed in the meantime!
3133 const defaultReplaceRange = getDefaultRange ( position , model ) ;
32-
3334 const providers = registry . all ( model ) ;
34- const providerResults = await Promise . all ( providers . map ( async provider => {
35+
36+ const multiMap = new SetMap < InlineCompletionProviderGroupId , InlineCompletionsProvider < any > > ( ) ;
37+ for ( const provider of providers ) {
38+ if ( provider . groupId ) {
39+ multiMap . add ( provider . groupId , provider ) ;
40+ }
41+ }
42+
43+ function getPreferredProviders ( provider : InlineCompletionsProvider < any > ) : InlineCompletionsProvider < any > [ ] {
44+ if ( ! provider . yieldsToGroupIds ) { return [ ] ; }
45+ const result : InlineCompletionsProvider < any > [ ] = [ ] ;
46+ for ( const groupId of provider . yieldsToGroupIds || [ ] ) {
47+ const providers = multiMap . get ( groupId ) ;
48+ for ( const p of providers ) {
49+ result . push ( p ) ;
50+ }
51+ }
52+ return result ;
53+ }
54+
55+ type Result = Promise < InlineCompletions < InlineCompletion > | null | undefined > ;
56+ const states = new Map < InlineCompletionsProvider < InlineCompletions < InlineCompletion > > , Result > ( ) ;
57+
58+ const seen = new Set < InlineCompletionsProvider < InlineCompletions < InlineCompletion > > > ( ) ;
59+ function findPreferredProviderCircle ( provider : InlineCompletionsProvider < any > , stack : InlineCompletionsProvider [ ] ) : InlineCompletionsProvider [ ] | undefined {
60+ stack = [ ...stack , provider ] ;
61+ if ( seen . has ( provider ) ) { return stack ; }
62+
63+ seen . add ( provider ) ;
3564 try {
36- const completions = await provider . provideInlineCompletions ( model , position , context , token ) ;
37- return ( { provider, completions } ) ;
38- } catch ( e ) {
39- onUnexpectedExternalError ( e ) ;
65+ const preferred = getPreferredProviders ( provider ) ;
66+ for ( const p of preferred ) {
67+ const c = findPreferredProviderCircle ( p , stack ) ;
68+ if ( c ) { return c ; }
69+ }
70+ } finally {
71+ seen . delete ( provider ) ;
4072 }
41- return ( { provider, completions : undefined } ) ;
42- } ) ) ;
73+ return undefined ;
74+ }
75+
76+ function processProvider ( provider : InlineCompletionsProvider < any > ) : Result {
77+ const state = states . get ( provider ) ;
78+ if ( state ) {
79+ return state ;
80+ }
81+
82+ const circle = findPreferredProviderCircle ( provider , [ ] ) ;
83+ if ( circle ) {
84+ onUnexpectedExternalError ( new Error ( `Inline completions: cyclic yield-to dependency detected. Path: ${ circle . map ( s => s . toString ? s . toString ( ) : ( '' + s ) ) . join ( ' -> ' ) } ` ) ) ;
85+ }
86+
87+ const deferredPromise = new DeferredPromise < InlineCompletions < InlineCompletion > | null | undefined > ( ) ;
88+ states . set ( provider , deferredPromise . p ) ;
89+
90+ ( async ( ) => {
91+ if ( ! circle ) {
92+ const preferred = getPreferredProviders ( provider ) ;
93+ for ( const p of preferred ) {
94+ const result = await processProvider ( p ) ;
95+ if ( result && result . items . length > 0 ) {
96+ // Skip provider
97+ return undefined ;
98+ }
99+ }
100+ }
101+
102+ try {
103+ const completions = await provider . provideInlineCompletions ( model , position , context , token ) ;
104+ return completions ;
105+ } catch ( e ) {
106+ onUnexpectedExternalError ( e ) ;
107+ return undefined ;
108+ }
109+ } ) ( ) . then ( c => deferredPromise . complete ( c ) , e => deferredPromise . error ( e ) ) ;
110+
111+ return deferredPromise . p ;
112+ }
113+
114+ const providerResults = await Promise . all ( providers . map ( async provider => ( { provider, completions : await processProvider ( provider ) } ) ) ) ;
43115
44116 const itemsByHash = new Map < string , InlineCompletionItem > ( ) ;
45117 const lists : InlineCompletionList [ ] = [ ] ;
0 commit comments