Skip to content

Commit d2250f3

Browse files
committed
QuickNavigate (#49340)
1 parent 3001f9a commit d2250f3

File tree

3 files changed

+91
-35
lines changed

3 files changed

+91
-35
lines changed

src/vs/platform/quickinput/common/quickInput.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
88
import { TPromise } from 'vs/base/common/winjs.base';
99
import { CancellationToken } from 'vs/base/common/cancellation';
10+
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
1011

1112
export interface IPickOpenEntry {
1213
id?: string;
@@ -16,6 +17,10 @@ export interface IPickOpenEntry {
1617
picked?: boolean;
1718
}
1819

20+
export interface IQuickNavigateConfiguration {
21+
keybindings: ResolvedKeybinding[];
22+
}
23+
1924
export interface IPickOptions {
2025

2126
/**
@@ -99,7 +104,7 @@ export interface IQuickInputService {
99104

100105
toggle(): void;
101106

102-
navigate(next: boolean): void;
107+
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void;
103108

104109
accept(): TPromise<void>;
105110

src/vs/workbench/browser/parts/quickinput/quickInput.ts

+83-32
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import 'vs/css!./quickInput';
99
import { Component } from 'vs/workbench/common/component';
10-
import { IQuickInputService, IPickOpenEntry, IPickOptions, IInputOptions } from 'vs/platform/quickinput/common/quickInput';
10+
import { IQuickInputService, IPickOpenEntry, IPickOptions, IInputOptions, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput';
1111
import { IPartService } from 'vs/workbench/services/part/common/partService';
1212
import * as dom from 'vs/base/browser/dom';
1313
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -73,6 +73,7 @@ export interface TextInputParameters extends BaseInputParameters {
7373
}
7474

7575
interface QuickInputUI {
76+
container: HTMLElement;
7677
checkAll: HTMLInputElement;
7778
inputBox: QuickInputBox;
7879
count: CountBadge;
@@ -95,9 +96,10 @@ class PickOneController<T extends IPickOpenEntry> implements InputController<T>
9596
public resolve: (ok?: true | Thenable<never>) => void;
9697
public progress: (value: T) => void;
9798
private closed = false;
99+
private quickNavigate = false;
98100
private disposables: IDisposable[] = [];
99101

100-
constructor(ui: QuickInputUI, parameters: PickOneParameters<T>) {
102+
constructor(private ui: QuickInputUI, parameters: PickOneParameters<T>) {
101103
this.result = new TPromise<T>((resolve, reject, progress) => {
102104
this.resolve = ok => resolve(ok === true ? <T>ui.list.getFocusedElements()[0] : ok);
103105
this.progress = progress;
@@ -143,6 +145,53 @@ class PickOneController<T extends IPickOpenEntry> implements InputController<T>
143145
});
144146
}
145147

148+
configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) {
149+
if (this.quickNavigate) {
150+
return;
151+
}
152+
this.quickNavigate = true;
153+
154+
this.disposables.push(dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
155+
const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent);
156+
const keyCode = keyboardEvent.keyCode;
157+
158+
// Select element when keys are pressed that signal it
159+
const quickNavKeys = quickNavigate.keybindings;
160+
const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
161+
const [firstPart, chordPart] = k.getParts();
162+
if (chordPart) {
163+
return false;
164+
}
165+
166+
if (firstPart.shiftKey && keyCode === KeyCode.Shift) {
167+
if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
168+
return false; // this is an optimistic check for the shift key being used to navigate back in quick open
169+
}
170+
171+
return true;
172+
}
173+
174+
if (firstPart.altKey && keyCode === KeyCode.Alt) {
175+
return true;
176+
}
177+
178+
if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) {
179+
return true;
180+
}
181+
182+
if (firstPart.metaKey && keyCode === KeyCode.Meta) {
183+
return true;
184+
}
185+
186+
return false;
187+
});
188+
189+
if (wasTriggerKeyPressed) {
190+
this.ui.close(true);
191+
}
192+
}));
193+
}
194+
146195
private dispose() {
147196
this.closed = true;
148197
this.disposables = dispose(this.disposables);
@@ -301,7 +350,6 @@ export class QuickInputService extends Component implements IQuickInputService {
301350
private static readonly MAX_WIDTH = 600; // Max total width of quick open widget
302351

303352
private layoutDimensions: dom.Dimension;
304-
private container: HTMLElement;
305353
private filterContainer: HTMLElement;
306354
private countContainer: HTMLElement;
307355
private okContainer: HTMLElement;
@@ -348,16 +396,16 @@ export class QuickInputService extends Component implements IQuickInputService {
348396
}
349397

350398
private create() {
351-
if (this.container) {
399+
if (this.ui) {
352400
return;
353401
}
354402

355403
const workbench = document.getElementById(this.partService.getWorkbenchElementId());
356-
this.container = dom.append(workbench, $('.quick-input-widget'));
357-
this.container.tabIndex = -1;
358-
this.container.style.display = 'none';
404+
const container = dom.append(workbench, $('.quick-input-widget'));
405+
container.tabIndex = -1;
406+
container.style.display = 'none';
359407

360-
const headerContainer = dom.append(this.container, $('.quick-input-header'));
408+
const headerContainer = dom.append(container, $('.quick-input-header'));
361409

362410
const checkAll = <HTMLInputElement>dom.append(headerContainer, $('input.quick-input-check-all'));
363411
checkAll.type = 'checkbox';
@@ -390,13 +438,13 @@ export class QuickInputService extends Component implements IQuickInputService {
390438
}
391439
}));
392440

393-
const message = dom.append(this.container, $('.quick-input-message'));
441+
const message = dom.append(container, $('.quick-input-message'));
394442

395-
this.progressBar = new ProgressBar(this.container);
443+
this.progressBar = new ProgressBar(container);
396444
dom.addClass(this.progressBar.getContainer(), 'quick-input-progress');
397445
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
398446

399-
const list = this.instantiationService.createInstance(QuickInputList, this.container);
447+
const list = this.instantiationService.createInstance(QuickInputList, container);
400448
this.toUnbind.push(list);
401449
this.toUnbind.push(list.onAllVisibleCheckedChanged(checked => {
402450
checkAll.checked = checked;
@@ -424,21 +472,21 @@ export class QuickInputService extends Component implements IQuickInputService {
424472
})
425473
);
426474

427-
this.toUnbind.push(dom.addDisposableListener(this.container, 'focusout', (e: FocusEvent) => {
428-
if (e.relatedTarget === this.container) {
475+
this.toUnbind.push(dom.addDisposableListener(container, 'focusout', (e: FocusEvent) => {
476+
if (e.relatedTarget === container) {
429477
(<HTMLElement>e.target).focus();
430478
return;
431479
}
432480
for (let element = <Element>e.relatedTarget; element; element = element.parentElement) {
433-
if (element === this.container) {
481+
if (element === container) {
434482
return;
435483
}
436484
}
437485
if (!this.ignoreFocusLost && !this.environmentService.args['sticky-quickopen'] && this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG)) {
438486
this.close(undefined, true);
439487
}
440488
}));
441-
this.toUnbind.push(dom.addDisposableListener(this.container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
489+
this.toUnbind.push(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
442490
const event = new StandardKeyboardEvent(e);
443491
switch (event.keyCode) {
444492
case KeyCode.Enter:
@@ -453,7 +501,7 @@ export class QuickInputService extends Component implements IQuickInputService {
453501
break;
454502
case KeyCode.Tab:
455503
if (!event.altKey && !event.ctrlKey && !event.metaKey) {
456-
const inputs = [].slice.call(this.container.querySelectorAll('input'))
504+
const inputs = [].slice.call(container.querySelectorAll('input'))
457505
.filter(input => input.style.display !== 'none');
458506
if (event.shiftKey && event.target === inputs[0]) {
459507
dom.EventHelper.stop(e, true);
@@ -469,7 +517,7 @@ export class QuickInputService extends Component implements IQuickInputService {
469517

470518
this.toUnbind.push(this.quickOpenService.onShow(() => this.close()));
471519

472-
this.ui = { checkAll, inputBox, count, message, list, close: ok => this.close(ok) };
520+
this.ui = { container, checkAll, inputBox, count, message, list, close: ok => this.close(ok) };
473521
this.updateStyles();
474522
}
475523

@@ -483,7 +531,7 @@ export class QuickInputService extends Component implements IQuickInputService {
483531
const result = resolved
484532
.then(() => {
485533
this.inQuickOpen('quickInput', false);
486-
this.container.style.display = 'none';
534+
this.ui.container.style.display = 'none';
487535
if (!focusLost) {
488536
this.restoreFocus();
489537
}
@@ -493,7 +541,7 @@ export class QuickInputService extends Component implements IQuickInputService {
493541
}
494542
}
495543
this.inQuickOpen('quickInput', false);
496-
this.container.style.display = 'none';
544+
this.ui.container.style.display = 'none';
497545
if (!focusLost) {
498546
this.restoreFocus();
499547
}
@@ -540,7 +588,7 @@ export class QuickInputService extends Component implements IQuickInputService {
540588
this.controller.resolve();
541589
}
542590

543-
this.container.setAttribute('data-type', parameters.type);
591+
this.ui.container.setAttribute('data-type', parameters.type);
544592

545593
this.ignoreFocusLost = parameters.ignoreFocusLost;
546594

@@ -556,10 +604,10 @@ export class QuickInputService extends Component implements IQuickInputService {
556604
this.ui.message.style.display = this.controller.showUI.message ? '' : 'none';
557605
this.ui.list.display(this.controller.showUI.list);
558606

559-
if (this.container.style.display === 'none') {
607+
if (this.ui.container.style.display === 'none') {
560608
this.inQuickOpen('quickInput', true);
561609
}
562-
this.container.style.display = '';
610+
this.ui.container.style.display = '';
563611
this.updateLayout();
564612
this.ui.inputBox.setFocus();
565613

@@ -608,9 +656,12 @@ export class QuickInputService extends Component implements IQuickInputService {
608656
}
609657
}
610658

611-
navigate(next: boolean) {
659+
navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) {
612660
if (this.isDisplayed() && this.ui.list.isDisplayed()) {
613661
this.ui.list.focus(next ? 'Next' : 'Previous');
662+
if (quickNavigate && this.controller instanceof PickOneController) {
663+
this.controller.configureQuickNavigate(quickNavigate);
664+
}
614665
}
615666
}
616667

@@ -628,11 +679,11 @@ export class QuickInputService extends Component implements IQuickInputService {
628679
}
629680

630681
private updateLayout() {
631-
if (this.layoutDimensions && this.container) {
682+
if (this.layoutDimensions && this.ui) {
632683
const titlebarOffset = this.partService.getTitleBarOffset();
633-
this.container.style.top = `${titlebarOffset}px`;
684+
this.ui.container.style.top = `${titlebarOffset}px`;
634685

635-
const style = this.container.style;
686+
const style = this.ui.container.style;
636687
const width = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickInputService.MAX_WIDTH);
637688
style.width = width + 'px';
638689
style.marginLeft = '-' + (width / 2) + 'px';
@@ -647,20 +698,20 @@ export class QuickInputService extends Component implements IQuickInputService {
647698
if (this.ui) {
648699
this.ui.inputBox.style(theme);
649700
}
650-
if (this.container) {
701+
if (this.ui) {
651702
const sideBarBackground = theme.getColor(SIDE_BAR_BACKGROUND);
652-
this.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined;
703+
this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined;
653704
const sideBarForeground = theme.getColor(SIDE_BAR_FOREGROUND);
654-
this.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined;
705+
this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined;
655706
const contrastBorderColor = theme.getColor(contrastBorder);
656-
this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined;
707+
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined;
657708
const widgetShadowColor = theme.getColor(widgetShadow);
658-
this.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined;
709+
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined;
659710
}
660711
}
661712

662713
private isDisplayed() {
663-
return this.container && this.container.style.display !== 'none';
714+
return this.ui && this.ui.container.style.display !== 'none';
664715
}
665716
}
666717

src/vs/workbench/browser/parts/quickopen/quickopen.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class BaseQuickOpenNavigateAction extends Action {
5858
const quickNavigate = this.quickNavigate ? { keybindings: keys } : void 0;
5959

6060
this.quickOpenService.navigate(this.next, quickNavigate);
61-
this.quickInputService.navigate(this.next);
61+
this.quickInputService.navigate(this.next, quickNavigate);
6262

6363
return TPromise.as(true);
6464
}
@@ -74,7 +74,7 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan
7474
const quickNavigate = { keybindings: keys };
7575

7676
quickOpenService.navigate(next, quickNavigate);
77-
quickInputService.navigate(next);
77+
quickInputService.navigate(next, quickNavigate);
7878
};
7979
}
8080

0 commit comments

Comments
 (0)