Skip to content

Commit 6943e61

Browse files
authored
improve discoverability of terminal suggest configuration (microsoft#252543)
fix microsoft#252321
1 parent 14a186b commit 6943e61

File tree

6 files changed

+181
-43
lines changed

6 files changed

+181
-43
lines changed

src/vs/editor/contrib/suggest/browser/suggestWidgetStatus.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class SuggestWidgetStatus {
3030
this.element = dom.append(container, dom.$('.suggest-status-bar'));
3131

3232
const actionViewItemProvider = <IActionViewItemProvider>(action => {
33-
return action instanceof MenuItemAction ? instantiationService.createInstance(TextOnlyMenuEntryActionViewItem, action, { useComma: true }) : undefined;
33+
return action instanceof MenuItemAction ? instantiationService.createInstance(TextOnlyMenuEntryActionViewItem, action, { useComma: false }) : undefined;
3434
});
3535
this._leftActions = new ActionBar(this.element, { actionViewItemProvider });
3636
this._rightActions = new ActionBar(this.element, { actionViewItemProvider });

src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { ITextModelService } from '../../../../../editor/common/services/resolve
3939
import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';
4040
import { env } from '../../../../../base/common/process.js';
4141
import { PYLANCE_DEBUG_DISPLAY_NAME } from './lspTerminalUtil.js';
42-
42+
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
4343

4444
registerSingleton(ITerminalCompletionService, TerminalCompletionService, InstantiationType.Delayed);
4545

@@ -286,6 +286,40 @@ registerTerminalAction({
286286
run: (c, accessor) => accessor.get(IPreferencesService).openSettings({ query: terminalSuggestConfigSection })
287287
});
288288

289+
registerTerminalAction({
290+
id: TerminalSuggestCommandId.LearnMore,
291+
title: localize2('workbench.action.terminal.learnMore', 'Learn More'),
292+
f1: false,
293+
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible),
294+
menu: {
295+
id: MenuId.MenubarTerminalSuggestStatusMenu,
296+
group: 'center',
297+
order: 1
298+
},
299+
keybinding: {
300+
primary: KeyMod.CtrlCmd | KeyCode.Slash,
301+
mac: { primary: KeyMod.WinCtrl | KeyCode.KeyK },
302+
weight: KeybindingWeight.WorkbenchContrib + 1
303+
},
304+
run: (c, accessor) => {
305+
(accessor.get(IOpenerService)).open('https://code.visualstudio.com/docs/terminal/shell-integration#_intellisense');
306+
}
307+
});
308+
309+
registerActiveInstanceAction({
310+
id: TerminalSuggestCommandId.ResetDiscoverability,
311+
title: localize2('workbench.action.terminal.resetDiscoverability', 'Reset Suggest Discoverability'),
312+
f1: true,
313+
precondition: ContextKeyExpr.and(
314+
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
315+
TerminalContextKeys.isOpen,
316+
ContextKeyExpr.equals(`config.${TerminalSuggestSettingId.Enabled}`, true)
317+
),
318+
run: (activeInstance) => {
319+
TerminalSuggestContribution.get(activeInstance)?.addon?.resetDiscoverability();
320+
}
321+
});
322+
289323
registerActiveInstanceAction({
290324
id: TerminalSuggestCommandId.RequestCompletions,
291325
title: localize2('workbench.action.terminal.requestCompletions', 'Request Completions'),

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

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { IntervalTimer, TimeoutTimer } from '../../../../../base/common/async.js
3535
import { localize } from '../../../../../nls.js';
3636
import { TerminalSuggestTelemetry } from './terminalSuggestTelemetry.js';
3737
import { terminalSymbolAliasIcon, terminalSymbolArgumentIcon, terminalSymbolEnumMember, terminalSymbolFileIcon, terminalSymbolFlagIcon, terminalSymbolInlineSuggestionIcon, terminalSymbolMethodIcon, terminalSymbolOptionIcon, terminalSymbolFolderIcon, terminalSymbolSymbolicLinkFileIcon, terminalSymbolSymbolicLinkFolderIcon } from './terminalSymbolIcons.js';
38+
import { TerminalSuggestShownTracker } from './terminalSuggestShownTracker.js';
3839

3940
export interface ISuggestController {
4041
isPasting: boolean;
@@ -46,9 +47,6 @@ export interface ISuggestController {
4647
hideSuggestWidget(cancelAnyRequests: boolean, wasClosedByUser?: boolean): void;
4748
}
4849

49-
50-
let firstShownTracker: { shell: Set<TerminalShellType>; window: boolean } | undefined = undefined;
51-
5250
export function isInlineCompletionSupported(shellType: TerminalShellType | undefined): boolean {
5351
if (!shellType) {
5452
return false;
@@ -89,6 +87,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
8987

9088
private _cancellationTokenSource: CancellationTokenSource | undefined;
9189

90+
private _discoverability: TerminalSuggestShownTracker | undefined;
91+
9292
isPasting: boolean = false;
9393
shellType: TerminalShellType | undefined;
9494
private readonly _shellTypeInit: Promise<void>;
@@ -161,7 +161,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
161161
@IConfigurationService private readonly _configurationService: IConfigurationService,
162162
@IInstantiationService private readonly _instantiationService: IInstantiationService,
163163
@IExtensionService private readonly _extensionService: IExtensionService,
164-
@ITerminalConfigurationService private readonly _terminalConfigurationService: ITerminalConfigurationService,
164+
@ITerminalConfigurationService private readonly _terminalConfigurationService: ITerminalConfigurationService
165165
) {
166166
super();
167167

@@ -231,7 +231,6 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
231231
this._model?.forceRefilterAll();
232232
}
233233
}));
234-
this._register(this._extensionService.onWillStop(() => firstShownTracker = undefined));
235234
}
236235

237236
activate(xterm: Terminal): void {
@@ -719,9 +718,9 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
719718
// Track the time when completions are shown for the first time
720719
if (this._completionRequestTimestamp !== undefined) {
721720
const completionLatency = Date.now() - this._completionRequestTimestamp;
722-
if (this._suggestTelemetry && this.shellType) {
723-
const firstShown = this.getFirstShown(this.shellType);
724-
this.updateShown();
721+
if (this._suggestTelemetry && this.shellType && this._discoverability) {
722+
const firstShown = this._discoverability.getFirstShown(this.shellType);
723+
this._discoverability.updateShown();
725724
this._suggestTelemetry.logCompletionLatency(this._sessionId, completionLatency, firstShown);
726725
}
727726
this._completionRequestTimestamp = undefined;
@@ -764,6 +763,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
764763
}));
765764
}
766765

766+
this._register(this._suggestWidget.onDidShow(() => this._updateDiscoverabilityState()));
767767
this._register(this._suggestWidget.onDidBlurDetails((e) => {
768768
const elt = e.relatedTarget as HTMLElement;
769769
if (this._terminal?.element?.contains(elt)) {
@@ -779,6 +779,21 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
779779
return this._suggestWidget;
780780
}
781781

782+
private _updateDiscoverabilityState(): void {
783+
if (!this._discoverability) {
784+
this._discoverability = this._register(this._instantiationService.createInstance(TerminalSuggestShownTracker, this.shellType));
785+
}
786+
787+
if (!this._suggestWidget || this._discoverability?.done) {
788+
return;
789+
}
790+
this._discoverability?.update(this._suggestWidget.element.domNode);
791+
}
792+
793+
resetDiscoverability(): void {
794+
this._discoverability?.resetState();
795+
}
796+
782797
selectPreviousSuggestion(): void {
783798
this._suggestWidget?.selectPrevious();
784799
}
@@ -901,36 +916,6 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
901916
this._suggestWidget?.hide();
902917
}
903918

904-
getFirstShown(shellType: TerminalShellType): { window: boolean; shell: boolean } {
905-
if (!firstShownTracker) {
906-
firstShownTracker = {
907-
window: true,
908-
shell: new Set([shellType])
909-
};
910-
return { window: true, shell: true };
911-
}
912-
913-
const isFirstForWindow = firstShownTracker.window;
914-
const isFirstForShell = !firstShownTracker.shell.has(shellType);
915-
916-
if (isFirstForWindow || isFirstForShell) {
917-
this.updateShown();
918-
}
919-
920-
return {
921-
window: isFirstForWindow,
922-
shell: isFirstForShell
923-
};
924-
}
925-
926-
updateShown(): void {
927-
if (!this.shellType || !firstShownTracker) {
928-
return;
929-
}
930-
931-
firstShownTracker.window = false;
932-
firstShownTracker.shell.add(this.shellType);
933-
}
934919
}
935920

936921
class PersistedWidgetSize {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
7+
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
8+
import { TerminalShellType } from '../../../../../platform/terminal/common/terminal.js';
9+
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
10+
11+
12+
export const TERMINAL_SUGGEST_DISCOVERABILITY_KEY = 'terminal.suggest.increasedDiscoverability';
13+
export const TERMINAL_SUGGEST_DISCOVERABILITY_COUNT_KEY = 'terminal.suggest.increasedDiscoverabilityCount';
14+
const TERMINAL_SUGGEST_DISCOVERABILITY_MAX_COUNT = 10;
15+
const TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS = 10000;
16+
17+
interface ITerminalSuggestShownTracker extends IDisposable {
18+
getFirstShown(shellType: TerminalShellType): { window: boolean; shell: boolean };
19+
updateShown(): void;
20+
resetState(): void;
21+
}
22+
23+
export class TerminalSuggestShownTracker extends Disposable implements ITerminalSuggestShownTracker {
24+
private _done: boolean;
25+
private _count: number;
26+
private _timeout: Timeout | undefined;
27+
private _start: number | undefined;
28+
29+
private _firstShownTracker: { shell: Set<TerminalShellType>; window: boolean } | undefined = undefined;
30+
31+
constructor(
32+
private readonly _shellType: TerminalShellType | undefined,
33+
@IStorageService private readonly _storageService: IStorageService,
34+
@IExtensionService private readonly _extensionService: IExtensionService
35+
36+
) {
37+
super();
38+
this._done = this._storageService.getBoolean(TERMINAL_SUGGEST_DISCOVERABILITY_KEY, StorageScope.APPLICATION, false);
39+
this._count = this._storageService.getNumber(TERMINAL_SUGGEST_DISCOVERABILITY_COUNT_KEY, StorageScope.APPLICATION, 0);
40+
this._register(this._extensionService.onWillStop(() => this._firstShownTracker = undefined));
41+
}
42+
43+
get done(): boolean {
44+
return this._done;
45+
}
46+
47+
resetState(): void {
48+
this._done = false;
49+
this._count = 0;
50+
this._start = undefined;
51+
this._firstShownTracker = undefined;
52+
}
53+
54+
update(widgetElt: HTMLElement | undefined): void {
55+
if (this._done) {
56+
return;
57+
}
58+
this._count++;
59+
this._storageService.store(TERMINAL_SUGGEST_DISCOVERABILITY_COUNT_KEY, this._count, StorageScope.APPLICATION, StorageTarget.USER);
60+
if (widgetElt && !widgetElt.classList.contains('increased-discoverability')) {
61+
widgetElt.classList.add('increased-discoverability');
62+
}
63+
if (this._count >= TERMINAL_SUGGEST_DISCOVERABILITY_MAX_COUNT) {
64+
this._setDone(widgetElt);
65+
} else if (!this._start) {
66+
this._start = Date.now();
67+
this._timeout = setTimeout(() => {
68+
this._setDone(widgetElt);
69+
}, TERMINAL_SUGGEST_DISCOVERABILITY_MIN_MS);
70+
}
71+
}
72+
73+
private _setDone(widgetElt: HTMLElement | undefined) {
74+
this._done = true;
75+
this._storageService.store(TERMINAL_SUGGEST_DISCOVERABILITY_KEY, true, StorageScope.APPLICATION, StorageTarget.USER);
76+
if (widgetElt) {
77+
widgetElt.classList.remove('increased-discoverability');
78+
}
79+
if (this._timeout) {
80+
clearTimeout(this._timeout);
81+
this._timeout = undefined;
82+
}
83+
this._start = undefined;
84+
}
85+
86+
getFirstShown(shellType: TerminalShellType): { window: boolean; shell: boolean } {
87+
if (!this._firstShownTracker) {
88+
this._firstShownTracker = {
89+
window: true,
90+
shell: new Set([shellType])
91+
};
92+
return { window: true, shell: true };
93+
}
94+
95+
const isFirstForWindow = this._firstShownTracker.window;
96+
const isFirstForShell = !this._firstShownTracker.shell.has(shellType);
97+
98+
if (isFirstForWindow || isFirstForShell) {
99+
this.updateShown();
100+
}
101+
102+
return {
103+
window: isFirstForWindow,
104+
shell: isFirstForShell
105+
};
106+
}
107+
108+
updateShown(): void {
109+
if (!this._shellType || !this._firstShownTracker) {
110+
return;
111+
}
112+
113+
this._firstShownTracker.window = false;
114+
this._firstShownTracker.shell.add(this._shellType);
115+
}
116+
}

src/vs/workbench/contrib/terminalContrib/suggest/common/terminal.suggest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const enum TerminalSuggestCommandId {
1717
ToggleDetails = 'workbench.action.terminal.suggestToggleDetails',
1818
ToggleDetailsFocus = 'workbench.action.terminal.suggestToggleDetailsFocus',
1919
ConfigureSettings = 'workbench.action.terminal.configureSuggestSettings',
20+
LearnMore = 'workbench.action.terminal.suggestLearnMore',
21+
ResetDiscoverability = 'workbench.action.terminal.resetDiscoverability'
2022
}
2123

2224
export const defaultTerminalSuggestCommandsToSkipShell = [

src/vs/workbench/services/suggest/browser/media/suggest.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@
8787
margin-right: 0;
8888
}
8989

90-
.monaco-workbench .workbench-suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label::after {
91-
content: ', ';
92-
margin-right: 0.3em;
90+
.monaco-workbench .workbench-suggest-widget.increased-discoverability.with-status-bar .suggest-status-bar .monaco-action-bar.right .actions-container .action-item:first-child .action-label {
91+
color: var(--vscode-button-foreground);
92+
background-color: var(--vscode-button-background);
9393
}
9494

95+
9596
.monaco-workbench .workbench-suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore,
9697
.monaco-workbench .workbench-suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused.string-label > .contents > .main > .right > .readMore {
9798
display: none;

0 commit comments

Comments
 (0)