Skip to content

Commit 8ed218a

Browse files
authored
Add loading indicator support to SimpleSuggestWidget (microsoft#251856)
1 parent 729baef commit 8ed218a

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export interface ITerminalCompletionService {
7272
_serviceBrand: undefined;
7373
readonly providers: IterableIterator<ITerminalCompletionProvider>;
7474
registerTerminalCompletionProvider(extensionIdentifier: string, id: string, provider: ITerminalCompletionProvider, ...triggerCharacters: string[]): IDisposable;
75-
provideCompletions(promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise<ITerminalCompletion[] | undefined>;
75+
provideCompletions(promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean, explicitlyInvoked?: boolean): Promise<ITerminalCompletion[] | undefined>;
7676
}
7777

7878
export class TerminalCompletionService extends Disposable implements ITerminalCompletionService {
@@ -122,7 +122,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
122122
});
123123
}
124124

125-
async provideCompletions(promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise<ITerminalCompletion[] | undefined> {
125+
async provideCompletions(promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean, explicitlyInvoked?: boolean): Promise<ITerminalCompletion[] | undefined> {
126126
if (!this._providers || !this._providers.values || cursorPosition < 0) {
127127
return undefined;
128128
}
@@ -148,7 +148,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
148148

149149
if (skipExtensionCompletions) {
150150
providers = providers.filter(p => p.isBuiltin);
151-
return this._collectCompletions(providers, shellType, promptValue, cursorPosition, allowFallbackCompletions, capabilities, token);
151+
return this._collectCompletions(providers, shellType, promptValue, cursorPosition, allowFallbackCompletions, capabilities, token, explicitlyInvoked);
152152
}
153153

154154
const providerConfig: { [key: string]: boolean } = this._configurationService.getValue(TerminalSuggestSettingId.Providers);
@@ -161,17 +161,18 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo
161161
return;
162162
}
163163

164-
return this._collectCompletions(providers, shellType, promptValue, cursorPosition, allowFallbackCompletions, capabilities, token);
164+
return this._collectCompletions(providers, shellType, promptValue, cursorPosition, allowFallbackCompletions, capabilities, token, explicitlyInvoked);
165165
}
166166

167-
private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, capabilities: ITerminalCapabilityStore, token: CancellationToken): Promise<ITerminalCompletion[] | undefined> {
167+
private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, allowFallbackCompletions: boolean, capabilities: ITerminalCapabilityStore, token: CancellationToken, explicitlyInvoked?: boolean): Promise<ITerminalCompletion[] | undefined> {
168168
const completionPromises = providers.map(async provider => {
169169
if (provider.shellTypes && !provider.shellTypes.includes(shellType)) {
170170
return undefined;
171171
}
172+
const timeoutMs = explicitlyInvoked ? 30000 : 5000;
172173
const completions = await Promise.race([
173174
provider.provideCompletions(promptValue, cursorPosition, allowFallbackCompletions, token),
174-
timeout(5000)
175+
timeout(timeoutMs)
175176
]);
176177
if (!completions) {
177178
return undefined;

src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,18 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
262262
};
263263
this._requestedCompletionsIndex = this._currentPromptInputState.cursorIndex;
264264

265+
// Show loading indicator before making async completion request (only for explicit invocations)
266+
if (explicitlyInvoked) {
267+
const suggestWidget = this._ensureSuggestWidget(terminal);
268+
const cursorPosition = this._getCursorPosition(terminal);
269+
if (cursorPosition) {
270+
suggestWidget.showTriggered(true, cursorPosition);
271+
}
272+
}
273+
265274
const quickSuggestionsConfig = this._configurationService.getValue<ITerminalSuggestConfiguration>(terminalSuggestConfigSection).quickSuggestions;
266275
const allowFallbackCompletions = explicitlyInvoked || quickSuggestionsConfig.unknown === 'on';
267-
const providedCompletions = await this._terminalCompletionService.provideCompletions(this._currentPromptInputState.prefix, this._currentPromptInputState.cursorIndex, allowFallbackCompletions, this.shellType, this._capabilities, token, doNotRequestExtensionCompletions);
276+
const providedCompletions = await this._terminalCompletionService.provideCompletions(this._currentPromptInputState.prefix, this._currentPromptInputState.cursorIndex, allowFallbackCompletions, this.shellType, this._capabilities, token, false, doNotRequestExtensionCompletions, explicitlyInvoked);
268277

269278
if (token.isCancellationRequested) {
270279
return;
@@ -544,16 +553,11 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
544553
return;
545554
}
546555

547-
const dimensions = this._getTerminalDimensions();
548-
if (!dimensions.width || !dimensions.height) {
556+
const cursorPosition = this._getCursorPosition(this._terminal);
557+
if (!cursorPosition) {
549558
return;
550559
}
551-
const xtermBox = this._screen!.getBoundingClientRect();
552-
this._suggestWidget.showSuggestions(0, false, true, {
553-
left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width,
554-
top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height,
555-
height: dimensions.height
556-
});
560+
this._suggestWidget.showSuggestions(0, false, true, cursorPosition);
557561
}
558562

559563
private _refreshInlineCompletion(completions: ITerminalCompletion[]): void {
@@ -603,6 +607,19 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
603607
};
604608
}
605609

610+
private _getCursorPosition(terminal: Terminal): { top: number; left: number; height: number } | undefined {
611+
const dimensions = this._getTerminalDimensions();
612+
if (!dimensions.width || !dimensions.height) {
613+
return undefined;
614+
}
615+
const xtermBox = this._screen!.getBoundingClientRect();
616+
return {
617+
left: xtermBox.left + terminal.buffer.active.cursorX * dimensions.width,
618+
top: xtermBox.top + terminal.buffer.active.cursorY * dimensions.height,
619+
height: dimensions.height
620+
};
621+
}
622+
606623
private _getFontInfo(): ISimpleSuggestWidgetFontInfo {
607624
if (this._cachedFontInfo) {
608625
return this._cachedFontInfo;
@@ -657,16 +674,11 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
657674
return;
658675
}
659676
this._model = model;
660-
const dimensions = this._getTerminalDimensions();
661-
if (!dimensions.width || !dimensions.height) {
677+
const cursorPosition = this._getCursorPosition(this._terminal);
678+
if (!cursorPosition) {
662679
return;
663680
}
664-
const xtermBox = this._screen!.getBoundingClientRect();
665-
suggestWidget.showSuggestions(0, false, !explicitlyInvoked, {
666-
left: xtermBox.left + this._terminal.buffer.active.cursorX * dimensions.width,
667-
top: xtermBox.top + this._terminal.buffer.active.cursorY * dimensions.height,
668-
height: dimensions.height
669-
});
681+
suggestWidget.showSuggestions(0, false, !explicitlyInvoked, cursorPosition);
670682
}
671683

672684

src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { LineContext, SimpleCompletionModel } from './simpleCompletionModel.js';
1313
import { getAriaId, SimpleSuggestWidgetItemRenderer, type ISimpleSuggestWidgetFontInfo } from './simpleSuggestWidgetRenderer.js';
1414
import { CancelablePromise, createCancelablePromise, disposableTimeout, TimeoutTimer } from '../../../../base/common/async.js';
1515
import { Emitter, Event, PauseableEmitter } from '../../../../base/common/event.js';
16-
import { MutableDisposable, Disposable } from '../../../../base/common/lifecycle.js';
16+
import { MutableDisposable, Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
1717
import { clamp } from '../../../../base/common/numbers.js';
1818
import { localize } from '../../../../nls.js';
1919
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
@@ -106,6 +106,8 @@ export class SimpleSuggestWidget<TModel extends SimpleCompletionModel<TItem>, TI
106106
private static NO_SUGGESTIONS_MESSAGE: string = localize('suggestWidget.noSuggestions', "No suggestions.");
107107

108108
private _state: State = State.Hidden;
109+
private _explicitlyInvoked: boolean = false;
110+
private _loadingTimeout?: IDisposable;
109111
private _completionModel?: TModel;
110112
private _cappedHeight?: { wanted: number; capped: number };
111113
private _forceRenderingAbove: boolean = false;
@@ -415,11 +417,22 @@ export class SimpleSuggestWidget<TModel extends SimpleCompletionModel<TItem>, TI
415417
this._persistedSize.reset();
416418
}
417419

420+
showTriggered(explicitlyInvoked: boolean, cursorPosition: { top: number; left: number; height: number }) {
421+
if (this._state !== State.Hidden) {
422+
return;
423+
}
424+
this._cursorPosition = cursorPosition;
425+
this._explicitlyInvoked = !!explicitlyInvoked;
426+
427+
if (this._explicitlyInvoked) {
428+
this._loadingTimeout = disposableTimeout(() => this._setState(State.Loading), 250);
429+
}
430+
}
431+
418432
showSuggestions(selectionIndex: number, isFrozen: boolean, isAuto: boolean, cursorPosition: { top: number; left: number; height: number }): void {
419433
this._cursorPosition = cursorPosition;
420434

421-
// this._contentWidget.setPosition(this.editor.getPosition());
422-
// this._loadingTimeout?.dispose();
435+
this._loadingTimeout?.dispose();
423436

424437
// this._currentSuggestionDetails?.cancel();
425438
// this._currentSuggestionDetails = undefined;
@@ -523,6 +536,7 @@ export class SimpleSuggestWidget<TModel extends SimpleCompletionModel<TItem>, TI
523536
this._details.hide();
524537
this._show();
525538
this._focusedItem = undefined;
539+
status(SimpleSuggestWidget.LOADING_MESSAGE);
526540
break;
527541
case State.Empty:
528542
this.element.domNode.classList.add('message');
@@ -657,7 +671,7 @@ export class SimpleSuggestWidget<TModel extends SimpleCompletionModel<TItem>, TI
657671
hide(): void {
658672
this._pendingLayout.clear();
659673
this._pendingShowDetails.clear();
660-
// this._loadingTimeout?.dispose();
674+
this._loadingTimeout?.dispose();
661675
this._ctxSuggestWidgetHasBeenNavigated.reset();
662676
this._ctxFirstSuggestionFocused.reset();
663677
this._setState(State.Hidden);

0 commit comments

Comments
 (0)