commandsHandler.ts 22.0 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

B
Benjamin Pasero 已提交
6 7 8
import { localize } from 'vs/nls';
import { distinct } from 'vs/base/common/arrays';
import { withNullAsUndefined, isFunction } from 'vs/base/common/types';
9
import { Language } from 'vs/base/common/platform';
10
import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
11 12
import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
13
import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
J
Joao Moreno 已提交
14
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
15
import { QuickOpenHandler, IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen';
B
Benjamin Pasero 已提交
16
import { IEditorAction } from 'vs/editor/common/editorCommon';
J
Johannes Rieken 已提交
17
import { matchesWords, matchesPrefix, matchesContiguousSubString, or } from 'vs/base/common/filters';
18
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
J
Johannes Rieken 已提交
19 20
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
J
Johannes Rieken 已提交
21
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
22
import { registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions';
B
Benjamin Pasero 已提交
23
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
D
Dirk Baeumer 已提交
24
import { LRUCache } from 'vs/base/common/map';
B
Benjamin Pasero 已提交
25 26
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
27
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
28
import { isPromiseCanceledError } from 'vs/base/common/errors';
29
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
30
import { INotificationService } from 'vs/platform/notification/common/notification';
31
import { CancellationToken } from 'vs/base/common/cancellation';
B
Benjamin Pasero 已提交
32
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
33
import { Disposable, DisposableStore, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
34
import { timeout } from 'vs/base/common/async';
B
Benjamin Pasero 已提交
35
import { isFirefox } from 'vs/base/browser/browser';
36
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
E
Erich Gamma 已提交
37

38
export const ALL_COMMANDS_PREFIX = '>';
E
Erich Gamma 已提交
39

D
Dirk Baeumer 已提交
40 41 42 43 44
interface ISerializedCommandHistory {
	usesLRU?: boolean;
	entries: { key: string; value: number }[];
}

B
Benjamin Pasero 已提交
45
class CommandsHistory extends Disposable {
46

B
Benjamin Pasero 已提交
47
	static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50;
48 49 50 51

	private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache';
	private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter';

B
Benjamin Pasero 已提交
52 53 54 55
	private static cache: LRUCache<string, number> | undefined;
	private static counter = 1;

	private configuredCommandsHistoryLength = 0;
56 57

	constructor(
58 59
		@IStorageService private readonly storageService: IStorageService,
		@IConfigurationService private readonly configurationService: IConfigurationService
60
	) {
B
Benjamin Pasero 已提交
61 62
		super();

63 64 65 66 67 68
		this.updateConfiguration();
		this.load();

		this.registerListeners();
	}

B
Benjamin Pasero 已提交
69 70 71 72
	private registerListeners(): void {
		this._register(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()));
	}

73
	private updateConfiguration(): void {
B
Benjamin Pasero 已提交
74
		this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService);
75

B
Benjamin Pasero 已提交
76 77
		if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) {
			CommandsHistory.cache.limit = this.configuredCommandsHistoryLength;
78 79

			CommandsHistory.saveState(this.storageService);
80 81 82 83
		}
	}

	private load(): void {
84
		const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL);
85
		let serializedCache: ISerializedCommandHistory | undefined;
86 87
		if (raw) {
			try {
D
Dirk Baeumer 已提交
88
				serializedCache = JSON.parse(raw);
89 90 91 92 93
			} catch (error) {
				// invalid data
			}
		}

B
Benjamin Pasero 已提交
94
		const cache = CommandsHistory.cache = new LRUCache<string, number>(this.configuredCommandsHistoryLength, 1);
D
Dirk Baeumer 已提交
95 96 97 98 99 100 101
		if (serializedCache) {
			let entries: { key: string; value: number }[];
			if (serializedCache.usesLRU) {
				entries = serializedCache.entries;
			} else {
				entries = serializedCache.entries.sort((a, b) => a.value - b.value);
			}
B
Benjamin Pasero 已提交
102
			entries.forEach(entry => cache.set(entry.key, entry.value));
D
Dirk Baeumer 已提交
103
		}
104

B
Benjamin Pasero 已提交
105
		CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter);
106 107
	}

B
Benjamin Pasero 已提交
108
	push(commandId: string): void {
B
Benjamin Pasero 已提交
109 110 111 112 113
		if (!CommandsHistory.cache) {
			return;
		}

		CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command
114 115

		CommandsHistory.saveState(this.storageService);
B
Benjamin Pasero 已提交
116 117
	}

118
	peek(commandId: string): number | undefined {
B
Benjamin Pasero 已提交
119
		return CommandsHistory.cache?.peek(commandId);
120 121
	}

122
	static saveState(storageService: IStorageService): void {
B
Benjamin Pasero 已提交
123 124 125 126
		if (!CommandsHistory.cache) {
			return;
		}

127
		const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] };
B
Benjamin Pasero 已提交
128
		CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value }));
B
Benjamin Pasero 已提交
129

130
		storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL);
B
Benjamin Pasero 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
		storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL);
	}

	static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number {
		const config = <IWorkbenchQuickOpenConfiguration>configurationService.getValue();

		const configuredCommandHistoryLength = config.workbench?.commandPalette?.history;
		if (typeof configuredCommandHistoryLength === 'number') {
			return configuredCommandHistoryLength;
		}

		return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH;
	}

	static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void {
		const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService);
		CommandsHistory.cache = new LRUCache<string, number>(commandHistoryLength);
		CommandsHistory.counter = 1;

		CommandsHistory.saveState(storageService);
151 152 153
	}
}

B
Benjamin Pasero 已提交
154 155
let lastCommandPaletteInput: string | undefined = undefined;

156
export class ShowAllCommandsAction extends Action {
E
Erich Gamma 已提交
157

B
Benjamin Pasero 已提交
158
	static readonly ID = 'workbench.action.showCommands';
B
Benjamin Pasero 已提交
159
	static readonly LABEL = localize('showTriggerActions', "Show All Commands");
160

161 162 163
	constructor(
		id: string,
		label: string,
164 165
		@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
		@IConfigurationService private readonly configurationService: IConfigurationService
166 167 168 169
	) {
		super(id, label);
	}

170
	run(): Promise<void> {
171
		const config = <IWorkbenchQuickOpenConfiguration>this.configurationService.getValue();
B
Benjamin Pasero 已提交
172
		const restoreInput = config.workbench?.commandPalette?.preserveInput === true;
173 174 175 176 177 178 179

		// Show with last command palette input if any and configured
		let value = ALL_COMMANDS_PREFIX;
		if (restoreInput && lastCommandPaletteInput) {
			value = `${value}${lastCommandPaletteInput}`;
		}

R
Rob Lourens 已提交
180
		this.quickOpenService.show(value, { inputSelection: lastCommandPaletteInput ? { start: 1 /* after prefix */, end: value.length } : undefined });
181

R
Rob Lourens 已提交
182
		return Promise.resolve(undefined);
183 184 185 186 187
	}
}

export class ClearCommandHistoryAction extends Action {

B
Benjamin Pasero 已提交
188
	static readonly ID = 'workbench.action.clearCommandHistory';
B
Benjamin Pasero 已提交
189
	static readonly LABEL = localize('clearCommandHistory', "Clear Command History");
190 191 192 193

	constructor(
		id: string,
		label: string,
194 195
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IStorageService private readonly storageService: IStorageService
196 197 198 199
	) {
		super(id, label);
	}

200
	run(): Promise<void> {
B
Benjamin Pasero 已提交
201
		const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService);
202
		if (commandHistoryLength > 0) {
B
Benjamin Pasero 已提交
203
			CommandsHistory.clearHistory(this.configurationService, this.storageService);
204 205
		}

R
Rob Lourens 已提交
206
		return Promise.resolve(undefined);
E
Erich Gamma 已提交
207 208 209
	}
}

210 211 212 213
class CommandPaletteEditorAction extends EditorAction {

	constructor() {
		super({
214
			id: ShowAllCommandsAction.ID,
B
Benjamin Pasero 已提交
215
			label: localize('showCommands.label', "Command Palette..."),
216
			alias: 'Command Palette',
217
			precondition: EditorContextKeys.editorSimpleInput.toNegated(),
218
			contextMenuOpts: {
B
Benjamin Pasero 已提交
219
				group: 'z_commands',
B
Benjamin Pasero 已提交
220
				order: 1
221
			}
222 223 224
		});
	}

J
Johannes Rieken 已提交
225
	run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
226 227 228 229 230
		const quickOpenService = accessor.get(IQuickOpenService);

		// Show with prefix
		quickOpenService.show(ALL_COMMANDS_PREFIX);

R
Rob Lourens 已提交
231
		return Promise.resolve(undefined);
232 233 234
	}
}

235
abstract class BaseCommandEntry extends QuickOpenEntryGroup {
B
Benjamin Pasero 已提交
236 237
	private description: string | undefined;
	private alias: string | undefined;
238
	private labelLowercase: string;
239
	private readonly keybindingAriaLabel?: string;
E
Erich Gamma 已提交
240 241

	constructor(
242
		private commandId: string,
B
Benjamin Pasero 已提交
243
		private keybinding: ResolvedKeybinding | undefined,
244
		private label: string,
B
Benjamin Pasero 已提交
245 246
		alias: string | undefined,
		highlights: { label: IHighlight[] | null, alias: IHighlight[] | null },
247
		private onBeforeRun: (commandId: string) => void,
248
		@INotificationService private readonly notificationService: INotificationService,
A
Alex Dima 已提交
249
		@ITelemetryService protected telemetryService: ITelemetryService
E
Erich Gamma 已提交
250 251 252
	) {
		super();

253
		this.labelLowercase = this.label.toLowerCase();
254
		this.keybindingAriaLabel = keybinding ? keybinding.getAriaLabel() || undefined : undefined;
255

256
		if (this.label !== alias) {
257 258
			this.alias = alias;
		} else {
B
Benjamin Pasero 已提交
259
			highlights.alias = null;
260 261
		}

B
Benjamin Pasero 已提交
262
		this.setHighlights(withNullAsUndefined(highlights.label), undefined, withNullAsUndefined(highlights.alias));
E
Erich Gamma 已提交
263 264
	}

B
Benjamin Pasero 已提交
265
	getCommandId(): string {
B
Benjamin Pasero 已提交
266 267 268
		return this.commandId;
	}

B
Benjamin Pasero 已提交
269
	getLabel(): string {
270 271 272
		return this.label;
	}

B
Benjamin Pasero 已提交
273
	getSortLabel(): string {
274 275 276
		return this.labelLowercase;
	}

B
Benjamin Pasero 已提交
277
	getDescription(): string | undefined {
278 279 280
		return this.description;
	}

B
Benjamin Pasero 已提交
281
	setDescription(description: string): void {
282 283 284
		this.description = description;
	}

B
Benjamin Pasero 已提交
285
	getKeybinding(): ResolvedKeybinding | undefined {
286 287 288
		return this.keybinding;
	}

B
Benjamin Pasero 已提交
289
	getDetail(): string | undefined {
290
		return this.alias;
E
Erich Gamma 已提交
291 292
	}

B
Benjamin Pasero 已提交
293
	getAriaLabel(): string {
294
		if (this.keybindingAriaLabel) {
B
Benjamin Pasero 已提交
295
			return localize('entryAriaLabelWithKey', "{0}, {1}, commands", this.getLabel(), this.keybindingAriaLabel);
296 297
		}

B
Benjamin Pasero 已提交
298
		return localize('entryAriaLabel', "{0}, commands", this.getLabel());
299 300
	}

B
Benjamin Pasero 已提交
301
	run(mode: Mode, context: IEntryRunContext): boolean {
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
		if (mode === Mode.OPEN) {
			this.runAction(this.getAction());

			return true;
		}

		return false;
	}

	protected abstract getAction(): Action | IEditorAction;

	protected runAction(action: Action | IEditorAction): void {

		// Indicate onBeforeRun
		this.onBeforeRun(this.commandId);
E
Erich Gamma 已提交
317

B
Benjamin Pasero 已提交
318
		const commandRunner = (async () => {
319
			if (action && (!(action instanceof Action) || action.enabled)) {
E
Erich Gamma 已提交
320
				try {
321
					this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: action.id, from: 'quick open' });
322 323 324 325 326 327 328 329 330

					const promise = action.run();
					if (promise) {
						try {
							await promise;
						} finally {
							if (action instanceof Action) {
								action.dispose();
							}
331
						}
332
					}
E
Erich Gamma 已提交
333 334 335 336
				} catch (error) {
					this.onError(error);
				}
			} else {
B
Benjamin Pasero 已提交
337
				this.notificationService.info(localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel()));
E
Erich Gamma 已提交
338
			}
B
Benjamin Pasero 已提交
339 340 341 342 343 344 345 346 347 348
		});

		// Use a timeout to give the quick open widget a chance to close itself first
		// Firefox: since the browser is quite picky for certain commands, we do not
		// use a timeout (https://github.com/microsoft/vscode/issues/83288)
		if (!isFirefox) {
			setTimeout(() => commandRunner(), 50);
		} else {
			commandRunner();
		}
E
Erich Gamma 已提交
349
	}
350 351 352 353 354 355

	private onError(error?: Error): void {
		if (isPromiseCanceledError(error)) {
			return;
		}

B
Benjamin Pasero 已提交
356
		this.notificationService.error(error || localize('canNotRun', "Command '{0}' resulted in an error.", this.label));
357
	}
E
Erich Gamma 已提交
358 359 360 361 362
}

class EditorActionCommandEntry extends BaseCommandEntry {

	constructor(
B
Benjamin Pasero 已提交
363
		commandId: string,
B
Benjamin Pasero 已提交
364
		keybinding: ResolvedKeybinding | undefined,
365
		label: string,
B
Benjamin Pasero 已提交
366 367
		meta: string | undefined,
		highlights: { label: IHighlight[] | null, alias: IHighlight[] | null },
368 369
		private action: IEditorAction,
		onBeforeRun: (commandId: string) => void,
370
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
371 372
		@ITelemetryService telemetryService: ITelemetryService
	) {
373
		super(commandId, keybinding, label, meta, highlights, onBeforeRun, notificationService, telemetryService);
E
Erich Gamma 已提交
374 375
	}

376 377
	protected getAction(): Action | IEditorAction {
		return this.action;
E
Erich Gamma 已提交
378 379 380 381 382 383
	}
}

class ActionCommandEntry extends BaseCommandEntry {

	constructor(
B
Benjamin Pasero 已提交
384
		commandId: string,
B
Benjamin Pasero 已提交
385
		keybinding: ResolvedKeybinding | undefined,
386
		label: string,
B
Benjamin Pasero 已提交
387 388
		alias: string | undefined,
		highlights: { label: IHighlight[] | null, alias: IHighlight[] | null },
389 390
		private action: Action,
		onBeforeRun: (commandId: string) => void,
391
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
392 393
		@ITelemetryService telemetryService: ITelemetryService
	) {
394
		super(commandId, keybinding, label, alias, highlights, onBeforeRun, notificationService, telemetryService);
E
Erich Gamma 已提交
395 396
	}

397 398
	protected getAction(): Action | IEditorAction {
		return this.action;
E
Erich Gamma 已提交
399 400 401
	}
}

402 403
const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString);

404
export class CommandsHandler extends QuickOpenHandler implements IDisposable {
405

B
Benjamin Pasero 已提交
406
	static readonly ID = 'workbench.picker.commands';
407

B
Benjamin Pasero 已提交
408
	private commandHistoryEnabled: boolean | undefined;
409 410 411
	private readonly commandsHistory: CommandsHistory;

	private readonly disposables = new DisposableStore();
412
	private readonly disposeOnClose = new DisposableStore();
413

B
Benjamin Pasero 已提交
414
	private waitedForExtensionsRegistered: boolean | undefined;
E
Erich Gamma 已提交
415 416

	constructor(
417 418 419 420 421 422
		@IEditorService private readonly editorService: IEditorService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IKeybindingService private readonly keybindingService: IKeybindingService,
		@IMenuService private readonly menuService: IMenuService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IExtensionService private readonly extensionService: IExtensionService
E
Erich Gamma 已提交
423 424
	) {
		super();
425

426
		this.commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory));
427

428
		this.extensionService.whenInstalledExtensionsRegistered().then(() => this.waitedForExtensionsRegistered = true);
429

430
		this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration());
431
		this.updateConfiguration();
E
Erich Gamma 已提交
432 433
	}

434
	private updateConfiguration(): void {
B
Benjamin Pasero 已提交
435
		this.commandHistoryEnabled = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService) > 0;
E
Erich Gamma 已提交
436 437
	}

438
	async getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
439
		if (this.waitedForExtensionsRegistered) {
440 441
			return this.doGetResults(searchValue, token);
		}
E
Erich Gamma 已提交
442

443 444 445 446
		// If extensions are not yet registered, we wait for a little moment to give them
		// a chance to register so that the complete set of commands shows up as result
		// We do not want to delay functionality beyond that time though to keep the commands
		// functional.
447 448
		await Promise.race([timeout(800).then(), this.extensionService.whenInstalledExtensionsRegistered()]);
		this.waitedForExtensionsRegistered = true;
449

450
		return this.doGetResults(searchValue, token);
451
	}
E
Erich Gamma 已提交
452

453 454 455 456
	private doGetResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
		if (token.isCancellationRequested) {
			return Promise.resolve(new QuickOpenModel([]));
		}
B
Benjamin Pasero 已提交
457

458
		searchValue = searchValue.trim();
E
Erich Gamma 已提交
459

460 461
		// Remember as last command palette input
		lastCommandPaletteInput = searchValue;
E
Erich Gamma 已提交
462

463
		// Editor Actions
464
		const activeTextEditorControl = this.editorService.activeTextEditorControl;
465
		let editorActions: IEditorAction[] = [];
466 467
		if (activeTextEditorControl && isFunction(activeTextEditorControl.getSupportedActions)) {
			editorActions = activeTextEditorControl.getSupportedActions();
468
		}
469

470
		const editorEntries = this.editorActionsToEntries(editorActions, searchValue);
E
Erich Gamma 已提交
471

472 473
		// Other Actions
		const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService)));
474 475 476
		const menuActions = menu.getActions()
			.reduce((r, [, actions]) => [...r, ...actions], <Array<MenuItemAction | SubmenuItemAction | string>>[])
			.filter(action => action instanceof MenuItemAction) as MenuItemAction[];
477 478
		const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue);
		menu.dispose();
479
		this.disposeOnClose.add(toDisposable(() => dispose(menuActions)));
480

481 482
		// Concat
		let entries = [...editorEntries, ...commandEntries];
483

484
		// Remove duplicates
B
Benjamin Pasero 已提交
485
		entries = distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`);
486 487 488 489 490 491 492 493 494 495 496

		// Handle label clashes
		const commandLabels = new Set<string>();
		entries.forEach(entry => {
			const commandLabel = `${entry.getLabel()}${entry.getGroupLabel()}`;
			if (commandLabels.has(commandLabel)) {
				entry.setDescription(entry.getCommandId());
			} else {
				commandLabels.add(commandLabel);
			}
		});
497

498 499 500 501
		// Sort by MRU order and fallback to name otherwie
		entries = entries.sort((elementA, elementB) => {
			const counterA = this.commandsHistory.peek(elementA.getCommandId());
			const counterB = this.commandsHistory.peek(elementB.getCommandId());
502

503 504 505
			if (counterA && counterB) {
				return counterA > counterB ? -1 : 1; // use more recently used command before older
			}
B
Benjamin Pasero 已提交
506

507 508 509
			if (counterA) {
				return -1; // first command was used, so it wins over the non used one
			}
B
Benjamin Pasero 已提交
510

511 512 513 514 515 516 517
			if (counterB) {
				return 1; // other command was used so it wins over the command
			}

			// both commands were never used, so we sort by name
			return elementA.getSortLabel().localeCompare(elementB.getSortLabel());
		});
518

519 520 521 522
		// Introduce group marker border between recently used and others
		// only if we have recently used commands in the result set
		const firstEntry = entries[0];
		if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) {
B
Benjamin Pasero 已提交
523
			firstEntry.setGroupLabel(localize('recentlyUsed', "recently used"));
524 525 526 527
			for (let i = 1; i < entries.length; i++) {
				const entry = entries[i];
				if (!this.commandsHistory.peek(entry.getCommandId())) {
					entry.setShowBorder(true);
B
Benjamin Pasero 已提交
528
					entry.setGroupLabel(localize('morecCommands', "other commands"));
529
					break;
530 531
				}
			}
532
		}
E
Erich Gamma 已提交
533

534
		return Promise.resolve(new QuickOpenModel(entries));
E
Erich Gamma 已提交
535 536
	}

A
Alex Dima 已提交
537
	private editorActionsToEntries(actions: IEditorAction[], searchValue: string): EditorActionCommandEntry[] {
B
Benjamin Pasero 已提交
538
		const entries: EditorActionCommandEntry[] = [];
E
Erich Gamma 已提交
539

540
		for (const action of actions) {
B
Benjamin Pasero 已提交
541 542 543
			if (action.id === ShowAllCommandsAction.ID) {
				continue; // avoid duplicates
			}
E
Erich Gamma 已提交
544

B
Benjamin Pasero 已提交
545
			const label = action.label;
546
			if (label) {
547

548
				// Alias for non default languages
B
Benjamin Pasero 已提交
549
				const alias = !Language.isDefaultVariant() ? action.alias : undefined;
B
Benjamin Pasero 已提交
550 551
				const labelHighlights = wordFilter(searchValue, label);
				const aliasHighlights = alias ? wordFilter(searchValue, alias) : null;
552

553
				if (labelHighlights || aliasHighlights) {
554
					entries.push(this.instantiationService.createInstance(EditorActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
E
Erich Gamma 已提交
555 556 557 558 559 560 561
				}
			}
		}

		return entries;
	}

562 563 564 565 566 567
	private onBeforeRunCommand(commandId: string): void {

		// Remember in commands history
		this.commandsHistory.push(commandId);
	}

568
	private menuItemActionsToEntries(actions: MenuItemAction[], searchValue: string): ActionCommandEntry[] {
B
Benjamin Pasero 已提交
569
		const entries: ActionCommandEntry[] = [];
E
Erich Gamma 已提交
570 571

		for (let action of actions) {
572
			const title = typeof action.title === 'string' ? action.title : action.title.value;
573
			let category, label = title;
574 575
			if (action.category) {
				category = typeof action.category === 'string' ? action.category : action.category.value;
B
Benjamin Pasero 已提交
576
				label = localize('cat.title', "{0}: {1}", category, title);
577 578
			}

579 580
			if (label) {
				const labelHighlights = wordFilter(searchValue, label);
581

582
				// Add an 'alias' in original language when running in different locale
583 584
				const aliasTitle = (!Language.isDefaultVariant() && typeof action.title !== 'string') ? action.title.original : undefined;
				const aliasCategory = (!Language.isDefaultVariant() && category && action.category && typeof action.category !== 'string') ? action.category.original : undefined;
J
Johannes Rieken 已提交
585 586 587 588 589 590
				let alias;
				if (aliasTitle && category) {
					alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`;
				} else if (aliasTitle) {
					alias = aliasTitle;
				}
591
				const aliasHighlights = alias ? wordFilter(searchValue, alias) : null;
592

593
				if (labelHighlights || aliasHighlights) {
594
					entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
595
				}
596
			}
E
Erich Gamma 已提交
597 598 599 600 601
		}

		return entries;
	}

B
Benjamin Pasero 已提交
602
	getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
603
		let autoFocusPrefixMatch: string | undefined = searchValue.trim();
604 605 606

		if (autoFocusPrefixMatch && this.commandHistoryEnabled) {
			const firstEntry = context.model && context.model.entries[0];
D
Dirk Baeumer 已提交
607
			if (firstEntry instanceof BaseCommandEntry && this.commandsHistory.peek(firstEntry.getCommandId())) {
R
Rob Lourens 已提交
608
				autoFocusPrefixMatch = undefined; // keep focus on MRU element if we have history elements
609 610 611
			}
		}

E
Erich Gamma 已提交
612 613
		return {
			autoFocusFirstEntry: true,
614
			autoFocusPrefixMatch
E
Erich Gamma 已提交
615 616 617
		};
	}

B
Benjamin Pasero 已提交
618
	getEmptyLabel(searchString: string): string {
B
Benjamin Pasero 已提交
619
		return localize('noCommandsMatching', "No commands matching");
E
Erich Gamma 已提交
620
	}
621

622 623 624 625 626 627
	onClose(canceled: boolean): void {
		super.onClose(canceled);

		this.disposeOnClose.clear();
	}

628 629
	dispose() {
		this.disposables.dispose();
630
		this.disposeOnClose.dispose();
631
	}
632
}
633

634
registerEditorAction(CommandPaletteEditorAction);