@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
247 lines (209 loc) • 9.04 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { inject, injectable } from 'inversify';
import { KeybindingRegistry } from '../keybinding';
import { Disposable, Command, CommandRegistry, CancellationToken, nls } from '../../common';
import { ContextKeyService } from '../context-key-service';
import { CorePreferences } from '../core-preferences';
import { QuickAccessContribution, QuickAccessProvider, QuickAccessRegistry } from './quick-access';
import { filterItems, QuickPickItem, QuickPicks } from './quick-input-service';
import { KeySequence } from '../keys';
import { codiconArray } from '../widgets';
export const quickCommand: Command = {
id: 'workbench.action.showCommands'
};
export const CLEAR_COMMAND_HISTORY = Command.toDefaultLocalizedCommand({
id: 'clear.command.history',
label: 'Clear Command History'
});
export const CLOSE_QUICK_OPEN: Command = {
id: 'workbench.action.closeQuickOpen'
};
export class QuickCommandService implements QuickAccessContribution, QuickAccessProvider {
static PREFIX = '>';
protected readonly contextKeyService: ContextKeyService;
protected readonly commandRegistry: CommandRegistry;
protected readonly corePreferences: CorePreferences;
protected readonly quickAccessRegistry: QuickAccessRegistry;
protected readonly keybindingRegistry: KeybindingRegistry;
// The list of exempted commands not to be displayed in the recently used list.
readonly exemptedCommands: Command[] = [
CLEAR_COMMAND_HISTORY,
];
private recentItems: QuickPickItem[] = [];
private otherItems: QuickPickItem[] = [];
registerQuickAccessProvider(): void {
this.quickAccessRegistry.registerQuickAccessProvider({
getInstance: () => this,
prefix: QuickCommandService.PREFIX,
placeholder: '',
helpEntries: [{ description: 'Quick Command', needsEditor: false }]
});
}
reset(): void {
const { recent, other } = this.getCommands();
this.recentItems = [];
this.otherItems = [];
this.recentItems.push(...recent.map(command => this.toItem(command)));
this.otherItems.push(...other.map(command => this.toItem(command)));
}
getPicks(filter: string, token: CancellationToken): QuickPicks {
const items: QuickPicks = [];
// Update the list of commands by fetching them from the registry.
this.reset();
const recentItems = filterItems(this.recentItems.slice(), filter);
const otherItems = filterItems(this.otherItems.slice(), filter);
if (recentItems.length > 0) {
items.push({ type: 'separator', label: nls.localizeByDefault('recently used') }, ...recentItems);
}
if (otherItems.length > 0) {
if (recentItems.length > 0) {
items.push({ type: 'separator', label: nls.localizeByDefault('other commands') });
}
items.push(...otherItems);
}
return items;
}
toItem(command: Command): QuickPickItem {
const label = (command.category) ? `${command.category}: ` + command.label! : command.label!;
const iconClasses = this.getItemIconClasses(command);
const activeElement = window.document.activeElement as HTMLElement;
const originalLabel = command.originalLabel || command.label!;
const originalCategory = command.originalCategory || command.category;
let detail: string | undefined = originalCategory ? `${originalCategory}: ${originalLabel}` : originalLabel;
if (label === detail) {
detail = undefined;
}
return {
label,
detail,
iconClasses,
alwaysShow: !!this.commandRegistry.getActiveHandler(command.id),
keySequence: this.getKeybinding(command),
execute: () => {
activeElement.focus({ preventScroll: true });
this.commandRegistry.executeCommand(command.id);
this.commandRegistry.addRecentCommand(command);
}
};
}
private getKeybinding(command: Command): KeySequence | undefined {
const keybindings = this.keybindingRegistry.getKeybindingsForCommand(command.id);
if (!keybindings || keybindings.length === 0) {
return undefined;
}
try {
return this.keybindingRegistry.resolveKeybinding(keybindings[0]);
} catch (error) {
return undefined;
}
}
private getItemIconClasses(command: Command): string[] | undefined {
const toggledHandler = this.commandRegistry.getToggledHandler(command.id);
if (toggledHandler) {
return codiconArray('check');
}
return undefined;
}
protected readonly contexts = new Map<string, string[]>();
pushCommandContext(commandId: string, when: string): Disposable {
const contexts = this.contexts.get(commandId) || [];
contexts.push(when);
this.contexts.set(commandId, contexts);
return Disposable.create(() => {
const index = contexts.indexOf(when);
if (index !== -1) {
contexts.splice(index, 1);
}
});
}
/**
* Get the list of valid commands.
*
* @param commands the list of raw commands.
* @returns the list of valid commands.
*/
protected getValidCommands(raw: Command[]): Command[] {
const valid: Command[] = [];
raw.forEach(command => {
if (command.label) {
const contexts = this.contexts.get(command.id);
if (!contexts || contexts.some(when => this.contextKeyService.match(when))) {
valid.push(command);
}
}
});
return valid;
}
/**
* Get the list of recently used and other commands.
*
* @returns the list of recently used commands and other commands.
*/
getCommands(): { recent: Command[], other: Command[] } {
// Get the list of recent commands.
const recentCommands = this.commandRegistry.recent;
// Get the list of all valid commands.
const allCommands = this.getValidCommands(this.commandRegistry.commands);
// Get the max history limit.
const limit = this.corePreferences['workbench.commandPalette.history'];
// Build the list of recent commands.
let rCommands: Command[] = [];
if (limit > 0) {
rCommands.push(...recentCommands.filter(r =>
!this.exemptedCommands.some(c => Command.equals(r, c)) &&
allCommands.some(c => Command.equals(r, c)))
);
if (rCommands.length > limit) {
rCommands = rCommands.slice(0, limit);
}
}
// Build the list of other commands.
const oCommands = allCommands.filter(c => !rCommands.some(r => Command.equals(r, c)));
// Normalize the list of recent commands.
const recent = this.normalize(rCommands);
// Normalize, and sort the list of other commands.
const other = this.sort(
this.normalize(oCommands)
);
return { recent, other };
}
/**
* Normalizes a list of commands.
* Normalization includes obtaining commands that have labels, are visible, and are enabled.
*
* @param commands the list of commands.
* @returns the list of normalized commands.
*/
private normalize(commands: Command[]): Command[] {
return commands.filter((a: Command) => a.label && (this.commandRegistry.isVisible(a.id) && this.commandRegistry.isEnabled(a.id)));
}
/**
* Sorts a list of commands alphabetically.
*
* @param commands the list of commands.
* @returns the list of sorted commands.
*/
private sort(commands: Command[]): Command[] {
return commands.sort((a: Command, b: Command) => Command.compareCommands(a, b));
}
}