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

import nls = require('vs/nls');
8
import { TPromise } from 'vs/base/common/winjs.base';
W
Will Prater 已提交
9
import * as objects from 'vs/base/common/objects';
E
Erich Gamma 已提交
10 11 12 13
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
import types = require('vs/base/common/types');
import errors = require('vs/base/common/errors');
14
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
15 16 17
import { Action } from 'vs/base/common/actions';
import { KeyMod } from 'vs/base/common/keyCodes';
import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen';
18
import { QuickOpenEntry, IHighlight, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
J
Johannes Rieken 已提交
19
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
20
import { IResourceInput, IEditorInput, IEditorOptions } from 'vs/platform/editor/common/editor';
J
Johannes Rieken 已提交
21
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Johannes Rieken 已提交
22
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
23
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
E
Erich Gamma 已提交
24

25 26
export const CLOSE_ON_FOCUS_LOST_CONFIG = 'workbench.quickOpen.closeOnFocusLost';

27 28 29 30 31 32 33 34 35
export interface IWorkbenchQuickOpenConfiguration {
	workbench: {
		commandPalette: {
			history: number;
			preserveInput: boolean;
		}
	};
}

E
Erich Gamma 已提交
36 37 38 39 40 41 42 43 44 45 46
export class QuickOpenHandler {

	/**
	 * A quick open handler returns results for a given input string. The resolved promise
	 * returns an instance of quick open model. It is up to the handler to keep and reuse an
	 * instance of the same model across multiple calls. This helps in situations where the user is
	 * narrowing down a search and the model is just filtering some items out.
	 *
	 * As such, returning the same model instance across multiple searches will yield best
	 * results in terms of performance when many items are shown.
	 */
47
	public getResults(searchValue: string): TPromise<IModel<any>> {
E
Erich Gamma 已提交
48 49 50
		return TPromise.as(null);
	}

51 52 53 54 55 56 57
	/**
	 * The ARIA label to apply when this quick open handler is active in quick open.
	 */
	public getAriaLabel(): string {
		return null;
	}

E
Erich Gamma 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
	/**
	 * Extra CSS class name to add to the quick open widget to do custom styling of entries.
	 */
	public getClass(): string {
		return null;
	}

	/**
	 * Indicates if the handler can run in the current environment. Return a string if the handler cannot run but has
	 * a good message to show in this case.
	 */
	public canRun(): boolean | string {
		return true;
	}

73 74 75 76 77 78 79
	/**
	 * Hints to the outside that this quick open handler typically returns results fast.
	 */
	public hasShortResponseTime(): boolean {
		return false;
	}

E
Erich Gamma 已提交
80 81 82 83
	/**
	 * Indicates if the handler wishes the quick open widget to automatically select the first result entry or an entry
	 * based on a specific prefix match.
	 */
84
	public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
E
Erich Gamma 已提交
85 86 87
		return {};
	}

88 89 90 91 92 93 94
	/**
	 * Indicates to the handler that the quick open widget has been opened.
	 */
	public onOpen(): void {
		return;
	}

E
Erich Gamma 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
	/**
	 * Indicates to the handler that the quick open widget has been closed. Allows to free up any resources as needed.
	 * The parameter canceled indicates if the quick open widget was closed with an entry being run or not.
	 */
	public onClose(canceled: boolean): void {
		return;
	}

	/**
	 * Allows to return a label that will be placed to the side of the results from this handler or null if none.
	 */
	public getGroupLabel(): string {
		return null;
	}

	/**
	 * Allows to return a label that will be used when there are no results found
	 */
	public getEmptyLabel(searchString: string): string {
		if (searchString.length > 0) {
			return nls.localize('noResultsMatching', "No results matching");
		}
		return nls.localize('noResultsFound2', "No results found");
	}
}

export interface QuickOpenHandlerHelpEntry {
	prefix: string;
	description: string;
	needsEditor: boolean;
}

/**
 * A lightweight descriptor of a quick open handler.
 */
130
export class QuickOpenHandlerDescriptor {
E
Erich Gamma 已提交
131 132
	public prefix: string;
	public description: string;
133
	public contextKey: string;
E
Erich Gamma 已提交
134 135 136
	public isDefault: boolean;
	public helpEntries: QuickOpenHandlerHelpEntry[];
	public instantProgress: boolean;
137

E
Erich Gamma 已提交
138
	private id: string;
139
	private ctor: IConstructorSignature0<QuickOpenHandler>;
E
Erich Gamma 已提交
140

141 142 143 144 145
	constructor(ctor: IConstructorSignature0<QuickOpenHandler>, id: string, prefix: string, contextKey: string, description: string, instantProgress?: boolean);
	constructor(ctor: IConstructorSignature0<QuickOpenHandler>, id: string, prefix: string, contextKey: string, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean);
	constructor(ctor: IConstructorSignature0<QuickOpenHandler>, id: string, prefix: string, contextKey: string, param: any, instantProgress: boolean = false) {
		this.ctor = ctor;
		this.id = id;
146 147
		this.prefix = prefix;
		this.contextKey = contextKey;
E
Erich Gamma 已提交
148 149 150 151 152 153 154 155 156 157 158 159
		this.instantProgress = instantProgress;

		if (types.isString(param)) {
			this.description = param;
		} else {
			this.helpEntries = param;
		}
	}

	public getId(): string {
		return this.id;
	}
160 161 162 163

	public instantiate(instantiationService: IInstantiationService): QuickOpenHandler {
		return instantiationService.createInstance(this.ctor);
	}
E
Erich Gamma 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
}

export const Extensions = {
	Quickopen: 'workbench.contributions.quickopen'
};

export interface IQuickOpenRegistry {

	/**
	 * Registers a quick open handler to the platform.
	 */
	registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void;

	/**
	 * Registers a default quick open handler to fallback to.
	 */
	registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void;

	/**
	 * Get all registered quick open handlers
	 */
	getQuickOpenHandlers(): QuickOpenHandlerDescriptor[];

	/**
	 * Get a specific quick open handler for a given prefix.
	 */
	getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor;

	/**
193
	 * Returns the default quick open handler.
E
Erich Gamma 已提交
194
	 */
195
	getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor;
E
Erich Gamma 已提交
196 197 198 199
}

class QuickOpenRegistry implements IQuickOpenRegistry {
	private handlers: QuickOpenHandlerDescriptor[];
200
	private defaultHandler: QuickOpenHandlerDescriptor;
E
Erich Gamma 已提交
201 202 203 204 205 206 207 208 209 210 211 212 213 214

	constructor() {
		this.handlers = [];
	}

	public registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void {
		this.handlers.push(descriptor);

		// sort the handlers by decreasing prefix length, such that longer
		// prefixes take priority: 'ext' vs 'ext install' - the latter should win
		this.handlers.sort((h1, h2) => h2.prefix.length - h1.prefix.length);
	}

	public registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void {
215
		this.defaultHandler = descriptor;
E
Erich Gamma 已提交
216 217 218 219 220 221 222 223 224 225
	}

	public getQuickOpenHandlers(): QuickOpenHandlerDescriptor[] {
		return this.handlers.slice(0);
	}

	public getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor {
		return text ? arrays.first(this.handlers, h => strings.startsWith(text, h.prefix), null) : null;
	}

226 227
	public getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor {
		return this.defaultHandler;
E
Erich Gamma 已提交
228 229 230 231 232 233 234 235 236 237
	}
}

Registry.add(Extensions.Quickopen, new QuickOpenRegistry());

export interface IEditorQuickOpenEntry {

	/**
	 * The editor input used for this entry when opening.
	 */
B
Benjamin Pasero 已提交
238
	getInput(): IResourceInput | IEditorInput;
E
Erich Gamma 已提交
239 240 241 242

	/**
	 * The editor options used for this entry when opening.
	 */
B
Benjamin Pasero 已提交
243
	getOptions(): IEditorOptions;
E
Erich Gamma 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
}

/**
 * A subclass of quick open entry that will open an editor with input and options when running.
 */
export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuickOpenEntry {

	constructor(private _editorService: IWorkbenchEditorService) {
		super();
	}

	public get editorService() {
		return this._editorService;
	}

B
Benjamin Pasero 已提交
259
	public getInput(): IResourceInput | IEditorInput {
E
Erich Gamma 已提交
260 261 262
		return null;
	}

B
Benjamin Pasero 已提交
263
	public getOptions(): IEditorOptions {
264
		return null;
E
Erich Gamma 已提交
265 266
	}

267
	public run(mode: Mode, context: IEntryRunContext): boolean {
268
		const hideWidget = (mode === Mode.OPEN);
W
Will Prater 已提交
269 270

		if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) {
271
			let sideBySide = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0;
W
Will Prater 已提交
272

273
			let openInBackgroundOptions: IEditorOptions;
W
Will Prater 已提交
274
			if (mode === Mode.OPEN_IN_BACKGROUND) {
275
				openInBackgroundOptions = { pinned: true, preserveFocus: true };
W
Will Prater 已提交
276
			}
E
Erich Gamma 已提交
277 278 279

			let input = this.getInput();
			if (input instanceof EditorInput) {
280 281
				let opts = this.getOptions();
				if (opts) {
B
Benjamin Pasero 已提交
282
					opts = objects.mixin(opts, openInBackgroundOptions, true);
283 284 285 286
				} else if (openInBackgroundOptions) {
					opts = EditorOptions.create(openInBackgroundOptions);
				}

W
Will Prater 已提交
287
				this.editorService.openEditor(input, opts, sideBySide).done(null, errors.onUnexpectedError);
288 289 290 291 292
			} else {
				const resourceInput = <IResourceInput>input;

				if (openInBackgroundOptions) {
					resourceInput.options = objects.assign(resourceInput.options || Object.create(null), openInBackgroundOptions);
W
Will Prater 已提交
293
				}
294 295

				this.editorService.openEditor(resourceInput, sideBySide).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
296 297 298
			}
		}

W
Will Prater 已提交
299
		return hideWidget;
E
Erich Gamma 已提交
300 301 302 303 304 305 306 307
	}
}

/**
 * A subclass of quick open entry group that provides access to editor input and options.
 */
export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry {

308
	public getInput(): IEditorInput | IResourceInput {
E
Erich Gamma 已提交
309 310 311
		return null;
	}

B
Benjamin Pasero 已提交
312
	public getOptions(): IEditorOptions {
E
Erich Gamma 已提交
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
		return null;
	}
}

// Infrastructure for quick open commands

export interface ICommand {
	aliases: string[];
	getResults(input: string): TPromise<QuickOpenEntry[]>;
	getEmptyLabel(input: string): string;
	icon?: string;
}

class CommandEntry extends QuickOpenEntry {

	constructor(private quickOpenService: IQuickOpenService, private prefix: string, private command: ICommand, highlights: IHighlight[]) {
		super(highlights);
		this.command = command;
	}

	public getIcon(): string {
		return this.command.icon || null;
	}

	public getLabel(): string {
		return this.command.aliases[0];
	}

341 342 343 344
	public getAriaLabel(): string {
		return nls.localize('entryAriaLabel', "{0}, command", this.getLabel());
	}

345
	public run(mode: Mode, context: IEntryRunContext): boolean {
E
Erich Gamma 已提交
346 347 348 349
		if (mode === Mode.PREVIEW) {
			return false;
		}

J
Johannes Rieken 已提交
350
		this.quickOpenService.show(`${this.prefix} ${this.command.aliases[0]} `);
E
Erich Gamma 已提交
351 352 353 354 355 356 357 358 359 360
		return false;
	}
}

export interface ICommandQuickOpenHandlerOptions {
	prefix: string;
	commands: ICommand[];
	defaultCommand?: ICommand;
}

361
export class QuickOpenAction extends Action {
E
Erich Gamma 已提交
362 363 364
	private prefix: string;

	constructor(
365 366 367 368
		id: string,
		label: string,
		prefix: string,
		@IQuickOpenService private quickOpenService: IQuickOpenService
E
Erich Gamma 已提交
369
	) {
370
		super(id, label);
B
Benjamin Pasero 已提交
371 372 373 374 375

		this.prefix = prefix;
		this.enabled = !!this.quickOpenService;
	}

B
Benjamin Pasero 已提交
376
	public run(context?: any): TPromise<void> {
B
Benjamin Pasero 已提交
377 378 379 380 381 382

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

		return TPromise.as(null);
	}
J
Johannes Rieken 已提交
383
}