Skip to content

Commit 8159b3e

Browse files
authored
add toggle details focus for terminal suggest widget (microsoft#238022)
1 parent 862fa30 commit 8159b3e

File tree

5 files changed

+64
-6
lines changed

5 files changed

+64
-6
lines changed

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { SuggestAddon } from './terminalSuggestAddon.js';
2828
import { TerminalClipboardContribution } from '../../clipboard/browser/terminal.clipboard.contribution.js';
2929
import { PwshCompletionProviderAddon } from './pwshCompletionProviderAddon.js';
3030
import { SimpleSuggestContext } from '../../../../services/suggest/browser/simpleSuggestWidget.js';
31+
import { SuggestDetailsClassName } from '../../../../services/suggest/browser/simpleSuggestWidgetDetails.js';
32+
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
3133

3234
registerSingleton(ITerminalCompletionService, TerminalCompletionService, InstantiationType.Delayed);
3335

@@ -153,7 +155,16 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo
153155
addon.setContainerWithOverflow(dom.findParentWithClass(xterm.element!, 'panel')!);
154156
}
155157
addon.setScreen(xterm.element!.querySelector('.xterm-screen')!);
156-
this.add(this._ctx.instance.onDidBlur(() => addon.hideSuggestWidget()));
158+
159+
this.add(dom.addDisposableListener(this._ctx.instance.domElement, dom.EventType.FOCUS_OUT, (e) => {
160+
const focusedElement = e.relatedTarget as HTMLElement;
161+
if (focusedElement.className === SuggestDetailsClassName) {
162+
// Don't hide the suggest widget if the focus is moving to the details
163+
return;
164+
}
165+
addon.hideSuggestWidget();
166+
}));
167+
157168
this.add(addon.onAcceptedCompletion(async text => {
158169
this._ctx.instance.focus();
159170
this._ctx.instance.sendText(text, false);
@@ -263,11 +274,25 @@ registerActiveInstanceAction({
263274
run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.toggleExplainMode()
264275
});
265276

277+
registerActiveInstanceAction({
278+
id: TerminalSuggestCommandId.ToggleDetailsFocus,
279+
title: localize2('workbench.action.terminal.suggestToggleDetailsFocus', 'Suggest Toggle Suggestion Focus'),
280+
f1: false,
281+
// HACK: This does not work with a precondition of `TerminalContextKeys.suggestWidgetVisible`, so make sure to not override the editor's keybinding
282+
precondition: EditorContextKeys.textInputFocus.negate(),
283+
keybinding: {
284+
weight: KeybindingWeight.WorkbenchContrib,
285+
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Space,
286+
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Space }
287+
},
288+
run: (activeInstance) => TerminalSuggestContribution.get(activeInstance)?.addon?.toggleSuggestionFocus()
289+
});
290+
266291
registerActiveInstanceAction({
267292
id: TerminalSuggestCommandId.ToggleDetails,
268293
title: localize2('workbench.action.terminal.suggestToggleDetails', 'Suggest Toggle Details'),
269294
f1: false,
270-
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus, TerminalContextKeys.isOpen, TerminalContextKeys.suggestWidgetVisible, SimpleSuggestContext.HasFocusedSuggestion),
295+
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus, TerminalContextKeys.suggestWidgetVisible, SimpleSuggestContext.HasFocusedSuggestion),
271296
keybinding: {
272297
// HACK: Force weight to be higher than that to start terminal chat
273298
weight: KeybindingWeight.ExternalExtension + 2,

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
230230
this._suggestWidget?.toggleExplainMode();
231231
}
232232

233+
toggleSuggestionFocus(): void {
234+
this._suggestWidget?.toggleDetailsFocus();
235+
}
236+
233237
toggleSuggestionDetails(): void {
234238
this._suggestWidget?.toggleDetails();
235239
}
@@ -373,6 +377,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
373377
}
374378
const suggestWidget = this._ensureSuggestWidget(this._terminal);
375379
suggestWidget.setCompletionModel(model);
380+
this._register(suggestWidget.onDidFocus(() => this._terminal?.focus()));
376381
if (!this._promptInputModel || !explicitlyInvoked && model.items.length === 0) {
377382
return;
378383
}
@@ -413,8 +418,29 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest
413418
listInactiveFocusOutline: activeContrastBorder
414419
}));
415420
this._register(this._suggestWidget.onDidSelect(async e => this.acceptSelectedSuggestion(e)));
416-
this._register(this._suggestWidget.onDidHide(() => this._terminalSuggestWidgetVisibleContextKey.set(false)));
421+
this._register(this._suggestWidget.onDidHide(() => this._terminalSuggestWidgetVisibleContextKey.reset()));
417422
this._register(this._suggestWidget.onDidShow(() => this._terminalSuggestWidgetVisibleContextKey.set(true)));
423+
424+
const element = this._terminal?.element?.querySelector('.xterm-helper-textarea');
425+
if (element) {
426+
this._register(dom.addDisposableListener(dom.getActiveDocument(), 'click', (event) => {
427+
const target = event.target as HTMLElement;
428+
if (this._terminal?.element?.contains(target)) {
429+
this._suggestWidget?.hide();
430+
}
431+
}));
432+
}
433+
434+
this._register(this._suggestWidget.onDidBlurDetails((e) => {
435+
const elt = e.relatedTarget as HTMLElement;
436+
if (this._terminal?.element?.contains(elt)) {
437+
// Do nothing, just the terminal getting focused
438+
// If there was a mouse click, the suggest widget will be
439+
// hidden above
440+
return;
441+
}
442+
this._suggestWidget?.hide();
443+
}));
418444
this._terminalSuggestWidgetVisibleContextKey.set(false);
419445
}
420446
return this._suggestWidget;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const enum TerminalSuggestCommandId {
1414
ClearSuggestCache = 'workbench.action.terminal.clearSuggestCache',
1515
RequestCompletions = 'workbench.action.terminal.requestCompletions',
1616
ResetWidgetSize = 'workbench.action.terminal.resetSuggestWidgetSize',
17-
ToggleDetails = 'workbench.action.terminal.suggestToggleDetails'
17+
ToggleDetails = 'workbench.action.terminal.suggestToggleDetails',
18+
ToggleDetailsFocus = 'workbench.action.terminal.suggestToggleDetailsFocus',
1819
}
1920

2021
export const defaultTerminalSuggestCommandsToSkipShell = [
@@ -27,5 +28,6 @@ export const defaultTerminalSuggestCommandsToSkipShell = [
2728
TerminalSuggestCommandId.HideSuggestWidget,
2829
TerminalSuggestCommandId.ClearSuggestCache,
2930
TerminalSuggestCommandId.RequestCompletions,
30-
TerminalSuggestCommandId.ToggleDetails
31+
TerminalSuggestCommandId.ToggleDetails,
32+
TerminalSuggestCommandId.ToggleDetailsFocus,
3133
];

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export class SimpleSuggestWidget extends Disposable {
9898
readonly onDidShow: Event<this> = this._onDidShow.event;
9999
private readonly _onDidFocus = new PauseableEmitter<ISimpleSelectedSuggestion>();
100100
readonly onDidFocus: Event<ISimpleSelectedSuggestion> = this._onDidFocus.event;
101+
private readonly _onDidBlurDetails = this._register(new Emitter<FocusEvent>());
102+
readonly onDidBlurDetails = this._onDidBlurDetails.event;
101103

102104
get list(): List<SimpleCompletionItem> { return this._list; }
103105

@@ -223,6 +225,7 @@ export class SimpleSuggestWidget extends Disposable {
223225
const details: SimpleSuggestDetailsWidget = this._register(instantiationService.createInstance(SimpleSuggestDetailsWidget));
224226
this._register(details.onDidClose(() => this.toggleDetails()));
225227
this._details = this._register(new SimpleSuggestDetailsOverlay(details, this._listElement));
228+
this._register(dom.addDisposableListener(this._details.widget.domNode, 'blur', (e) => this._onDidBlurDetails.fire(e)));
226229

227230
if (options.statusBarMenuId) {
228231
this._status = this._register(instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode, options.statusBarMenuId));

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export function canExpandCompletionItem(item: SimpleCompletionItem | undefined):
2121
return !!item && Boolean(item.completion.detail && item.completion.detail !== item.completion.label);
2222
}
2323

24+
export const SuggestDetailsClassName = 'suggest-details';
25+
2426
export class SimpleSuggestDetailsWidget {
2527

2628
readonly domNode: HTMLDivElement;
@@ -158,7 +160,7 @@ export class SimpleSuggestDetailsWidget {
158160
this._renderDisposeable.add(renderedContents);
159161
}
160162

161-
// this.domNode.classList.toggle('detail-and-doc', !!documentation);
163+
this.domNode.classList.toggle('detail-and-doc', !!detail && !!documentation);
162164

163165
this.domNode.style.userSelect = 'text';
164166
this.domNode.tabIndex = -1;

0 commit comments

Comments
 (0)