searchEditor.contribution.ts 15.8 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
7
import { extname } from 'vs/base/common/resources';
J
Jackson Kearl 已提交
8
import { URI } from 'vs/base/common/uri';
9
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
10
import { Range } from 'vs/editor/common/core/range';
11 12
import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
import { localize } from 'vs/nls';
13
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
14
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
15 16
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
J
Jackson Kearl 已提交
17
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
18 19 20
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
J
Jackson Kearl 已提交
21
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
22
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
J
Jackson Kearl 已提交
23
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
24
import { ActiveEditorContext, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
25 26 27
import { IViewsService } from 'vs/workbench/common/views';
import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions';
import { searchRefreshIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
28
import * as SearchConstants from 'vs/workbench/contrib/search/common/constants';
J
Jackson Kearl 已提交
29
import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants';
30
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
31
import { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
32
import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput, SEARCH_EDITOR_EXT } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput';
33
import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization';
34
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

const OpenInEditorCommandId = 'search.action.openInEditor';
const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';

const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex';
const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines';
const IncreaseSearchEditorContextLinesCommandId = 'increaseSearchEditorContextLines';
const DecreaseSearchEditorContextLinesCommandId = 'decreaseSearchEditorContextLines';

const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch';
const CleanSearchEditorStateCommandId = 'cleanSearchEditorState';
const SelectAllSearchEditorMatchesCommandId = 'selectAllSearchEditorMatches';



54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
//#region Editor Descriptior
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
	EditorDescriptor.create(
		SearchEditor,
		SearchEditor.ID,
		localize('searchEditor', "Search Editor")
	),
	[
		new SyncDescriptor(SearchEditorInput)
	]
);
//#endregion

//#region Startup Contribution
class SearchEditorContribution implements IWorkbenchContribution {
	constructor(
		@IEditorService private readonly editorService: IEditorService,
		@IInstantiationService protected readonly instantiationService: IInstantiationService,
		@ITelemetryService protected readonly telemetryService: ITelemetryService,
		@IContextKeyService protected readonly contextKeyService: IContextKeyService,
	) {

R
rebornix 已提交
76 77
		this.editorService.overrideOpenEditor({
			open: (editor, options, group) => {
78
				const resource = editor.resource;
R
rebornix 已提交
79
				if (!resource) { return undefined; }
J
Jackson Kearl 已提交
80

81
				if (extname(resource) !== SEARCH_EDITOR_EXT) {
R
rebornix 已提交
82 83
					return undefined;
				}
84

85
				if (editor instanceof SearchEditorInput && group.isOpened(editor)) {
R
rebornix 已提交
86 87
					return undefined;
				}
88

R
rebornix 已提交
89
				this.telemetryService.publicLog2('searchEditor/openSavedSearchEditor');
J
Jackson Kearl 已提交
90

R
rebornix 已提交
91 92 93 94
				return {
					override: (async () => {
						const { config } = await instantiationService.invokeFunction(parseSavedSearchEditor, resource);
						const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { backingUri: resource, config });
95
						return editorService.openEditor(input, { ...options, override: false }, group);
R
rebornix 已提交
96 97 98
					})()
				};
			}
99 100 101 102 103 104 105 106 107
		});
	}
}

const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting);
//#endregion

//#region Input Factory
108
type SerializedSearchEditor = { modelUri: string, dirty: boolean, config: SearchConfiguration, name: string, matchRanges: Range[], backingUri: string };
109 110
class SearchEditorInputFactory implements IEditorInputFactory {

111 112 113
	canSerialize(input: SearchEditorInput) {
		return !input.isDisposed();
	}
114 115

	serialize(input: SearchEditorInput) {
116 117 118
		let modelUri = undefined;
		if (input.modelUri.path || input.modelUri.fragment) {
			modelUri = input.modelUri.toString();
119
		}
120
		if (!modelUri) { return undefined; }
121

122
		const config = input.config;
123
		const dirty = input.isDirty();
124
		const matchRanges = input.getMatchRanges();
125
		const backingUri = input.backingUri;
126

127
		return JSON.stringify({ modelUri: modelUri.toString(), dirty, config, name: input.getName(), matchRanges, backingUri: backingUri?.toString() } as SerializedSearchEditor);
128 129 130
	}

	deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined {
131 132 133
		const { modelUri, dirty, config, matchRanges, backingUri } = JSON.parse(serializedEditorInput) as SerializedSearchEditor;
		if (config && (config.query !== undefined) && (modelUri !== undefined)) {
			const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config, modelUri: URI.parse(modelUri), backingUri: backingUri ? URI.parse(backingUri) : undefined });
134
			input.setDirty(dirty);
135
			input.setMatchRanges(matchRanges);
136 137 138 139 140 141 142 143 144 145 146 147
			return input;
		}
		return undefined;
	}
}

Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(
	SearchEditorInput.ID,
	SearchEditorInputFactory);
//#endregion

//#region Commands
148
KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({
149
	id: ToggleSearchEditorCaseSensitiveCommandId,
150 151 152 153 154
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
	handler: toggleSearchEditorCaseSensitiveCommand
}, ToggleCaseSensitiveKeybinding));

155
KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({
156
	id: ToggleSearchEditorWholeWordCommandId,
157 158 159 160 161
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
	handler: toggleSearchEditorWholeWordCommand
}, ToggleWholeWordKeybinding));

162
KeybindingsRegistry.registerCommandAndKeybindingRule(Object.assign({
163
	id: ToggleSearchEditorRegexCommandId,
164 165 166 167 168 169
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey),
	handler: toggleSearchEditorRegexCommand
}, ToggleRegexKeybinding));

KeybindingsRegistry.registerCommandAndKeybindingRule({
170
	id: ToggleSearchEditorContextLinesCommandId,
171 172 173 174 175 176
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
	handler: toggleSearchEditorContextLinesCommand,
	primary: KeyMod.Alt | KeyCode.KEY_L,
	mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L }
});
177

178
KeybindingsRegistry.registerCommandAndKeybindingRule({
179
	id: IncreaseSearchEditorContextLinesCommandId,
180 181 182
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
	handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true),
J
Jackson Kearl 已提交
183
	primary: KeyMod.Alt | KeyCode.US_EQUAL
184 185 186
});

KeybindingsRegistry.registerCommandAndKeybindingRule({
187
	id: DecreaseSearchEditorContextLinesCommandId,
188 189 190
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
	handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false),
J
Jackson Kearl 已提交
191
	primary: KeyMod.Alt | KeyCode.US_MINUS
192 193
});

194
KeybindingsRegistry.registerCommandAndKeybindingRule({
195
	id: SelectAllSearchEditorMatchesCommandId,
196 197 198 199 200 201
	weight: KeybindingWeight.WorkbenchContrib,
	when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor),
	primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L,
	handler: selectAllSearchEditorMatchesCommand
});

202
CommandsRegistry.registerCommand(
203
	CleanSearchEditorStateCommandId,
204
	(accessor: ServicesAccessor) => {
205 206 207
		const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
		if (activeEditorPane instanceof SearchEditor) {
			activeEditorPane.cleanState();
208 209
		}
	});
210 211 212
//#endregion

//#region Actions
B
Benjamin Pasero 已提交
213
const category = { value: localize('search', "Search Editor"), original: 'Search Editor' };
214

J
Jackson Kearl 已提交
215
export type OpenSearchEditorArgs = Partial<SearchConfiguration & { triggerSearch: boolean, focusResults: boolean, location: 'reuse' | 'new' }>;
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
const openArgDescription = {
	description: 'Open a new search editor. Arguments passed can include variables like ${relativeFileDirname}.',
	args: [{
		name: 'Open new Search Editor args',
		schema: {
			properties: {
				query: { type: 'string' },
				includes: { type: 'string' },
				excludes: { type: 'string' },
				contextLines: { type: 'number' },
				wholeWord: { type: 'boolean' },
				caseSensitive: { type: 'boolean' },
				regexp: { type: 'boolean' },
				useIgnores: { type: 'boolean' },
				showIncludesExcludes: { type: 'boolean' },
231 232
				triggerSearch: { type: 'boolean' },
				focusResults: { type: 'boolean' },
233 234 235 236 237
			}
		}
	}]
} as const;

238 239 240 241
registerAction2(class extends Action2 {
	constructor() {
		super({
			id: 'search.searchEditor.action.deleteFileResults',
B
Benjamin Pasero 已提交
242
			title: { value: localize('searchEditor.deleteResultBlock', "Delete File Results"), original: 'Delete File Results' },
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
			keybinding: {
				weight: KeybindingWeight.EditorContrib,
				when: SearchEditorConstants.InSearchEditor,
				primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace,
			},
			precondition: SearchEditorConstants.InSearchEditor,
			category,
			f1: true,
		});
	}

	async run(accessor: ServicesAccessor) {
		const contextService = accessor.get(IContextKeyService).getContext(document.activeElement);
		if (contextService.getValue(SearchEditorConstants.InSearchEditor.serialize())) {
			(accessor.get(IEditorService).activeEditorPane as SearchEditor).deleteResultBlock();
		}
	}
});

262 263 264 265
registerAction2(class extends Action2 {
	constructor() {
		super({
			id: SearchEditorConstants.OpenNewEditorCommandId,
J
Jackson Kearl 已提交
266
			title: { value: localize('search.openNewSearchEditor', "New Search Editor"), original: 'New Search Editor' },
267 268
			category,
			f1: true,
269
			description: openArgDescription
270 271
		});
	}
272
	async run(accessor: ServicesAccessor, args: OpenSearchEditorArgs) {
J
Jackson Kearl 已提交
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
		await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, { ...args, location: 'new' });
	}
});

registerAction2(class extends Action2 {
	constructor() {
		super({
			id: SearchEditorConstants.OpenEditorCommandId,
			title: { value: localize('search.openSearchEditor', "Open Search Editor"), original: 'Open Search Editor' },
			category,
			f1: true,
			description: openArgDescription
		});
	}
	async run(accessor: ServicesAccessor, args: OpenSearchEditorArgs) {
		await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, { ...args, location: 'reuse' });
289 290 291 292 293 294 295
	}
});

registerAction2(class extends Action2 {
	constructor() {
		super({
			id: OpenNewEditorToSideCommandId,
B
Benjamin Pasero 已提交
296
			title: { value: localize('search.openNewEditorToSide', "Open new Search Editor to the Side"), original: 'Open new Search Editor to the Side' },
297 298
			category,
			f1: true,
299
			description: openArgDescription
300 301
		});
	}
302
	async run(accessor: ServicesAccessor, args: OpenSearchEditorArgs) {
303 304 305
		await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args, true);
	}
});
306

307 308 309
registerAction2(class extends Action2 {
	constructor() {
		super({
310
			id: OpenInEditorCommandId,
B
Benjamin Pasero 已提交
311
			title: { value: localize('search.openResultsInEditor', "Open Results in Editor"), original: 'Open Results in Editor' },
312 313 314
			category,
			f1: true,
			keybinding: {
J
Jackson Kearl 已提交
315
				primary: KeyMod.Alt | KeyCode.Enter,
316
				when: ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey),
J
Jackson Kearl 已提交
317 318 319 320
				weight: KeybindingWeight.WorkbenchContrib,
				mac: {
					primary: KeyMod.CtrlCmd | KeyCode.Enter
				}
321 322 323 324 325 326 327 328 329 330 331 332
			},
		});
	}
	async run(accessor: ServicesAccessor) {
		const viewsService = accessor.get(IViewsService);
		const instantiationService = accessor.get(IInstantiationService);
		const searchView = getSearchView(viewsService);
		if (searchView) {
			await instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue());
		}
	}
});
333

334 335 336
registerAction2(class extends Action2 {
	constructor() {
		super({
337
			id: RerunSearchEditorSearchCommandId,
B
Benjamin Pasero 已提交
338
			title: { value: localize('search.rerunSearchInEditor', "Search Again"), original: 'Search Again' },
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
			category,
			keybinding: {
				primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R,
				when: SearchEditorConstants.InSearchEditor,
				weight: KeybindingWeight.EditorContrib
			},
			icon: searchRefreshIcon,
			menu: [{
				id: MenuId.EditorTitle,
				group: 'navigation',
				when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)
			},
			{
				id: MenuId.CommandPalette,
				when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)
			}]
		});
	}
	async run(accessor: ServicesAccessor) {
		const editorService = accessor.get(IEditorService);
		const input = editorService.activeEditor;
		if (input instanceof SearchEditorInput) {
			(editorService.activeEditorPane as SearchEditor).triggerSearch({ resetCursor: false });
		}
	}
});
365

366 367 368
registerAction2(class extends Action2 {
	constructor() {
		super({
369
			id: FocusQueryEditorWidgetCommandId,
B
Benjamin Pasero 已提交
370
			title: { value: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"), original: 'Focus Search Editor Input' },
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
			category,
			menu: {
				id: MenuId.CommandPalette,
				when: ActiveEditorContext.isEqualTo(SearchEditorConstants.SearchEditorID)
			},
			keybinding: {
				primary: KeyCode.Escape,
				when: SearchEditorConstants.InSearchEditor,
				weight: KeybindingWeight.EditorContrib
			}
		});
	}
	async run(accessor: ServicesAccessor) {
		const editorService = accessor.get(IEditorService);
		const input = editorService.activeEditor;
		if (input instanceof SearchEditorInput) {
			(editorService.activeEditorPane as SearchEditor).focusSearchInput();
		}
	}
390
});
391
//#endregion