/*--------------------------------------------------------------------------------------------- * 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'; import * as objects from 'vs/base/common/objects'; import { extname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; 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'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { ActiveEditorContext, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; 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'; import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, selectAllSearchEditorMatchesCommand, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchConfiguration, SearchEditorInput, SEARCH_EDITOR_EXT } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { parseSavedSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; 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'; //#region Editor Descriptior Registry.as(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, ) { this.editorService.overrideOpenEditor({ open: (editor, options, group) => { const resource = editor.resource; if (!resource) { return undefined; } if (extname(resource) !== SEARCH_EDITOR_EXT) { return undefined; } if (editor instanceof SearchEditorInput && group.isOpened(editor)) { return undefined; } this.telemetryService.publicLog2('searchEditor/openSavedSearchEditor'); return { override: (async () => { const { config } = await instantiationService.invokeFunction(parseSavedSearchEditor, resource); const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { backingUri: resource, config }); return editorService.openEditor(input, { ...options, override: false }, group); })() }; } }); } } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); //#endregion //#region Input Factory type SerializedSearchEditor = { modelUri: string, dirty: boolean, config: SearchConfiguration, name: string, matchRanges: Range[], backingUri: string }; class SearchEditorInputFactory implements IEditorInputFactory { canSerialize(input: SearchEditorInput) { return !input.isDisposed(); } serialize(input: SearchEditorInput) { let modelUri = undefined; if (input.modelUri.path || input.modelUri.fragment) { modelUri = input.modelUri.toString(); } if (!modelUri) { return undefined; } const config = input.config; const dirty = input.isDirty(); const matchRanges = input.getMatchRanges(); const backingUri = input.backingUri; return JSON.stringify({ modelUri: modelUri.toString(), dirty, config, name: input.getName(), matchRanges, backingUri: backingUri?.toString() } as SerializedSearchEditor); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { 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 }); input.setDirty(dirty); input.setMatchRanges(matchRanges); return input; } return undefined; } } Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( SearchEditorInput.ID, SearchEditorInputFactory); //#endregion //#region Commands KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: ToggleSearchEditorCaseSensitiveCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), handler: toggleSearchEditorCaseSensitiveCommand }, ToggleCaseSensitiveKeybinding)); KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: ToggleSearchEditorWholeWordCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), handler: toggleSearchEditorWholeWordCommand }, ToggleWholeWordKeybinding)); KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: ToggleSearchEditorRegexCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), handler: toggleSearchEditorRegexCommand }, ToggleRegexKeybinding)); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: ToggleSearchEditorContextLinesCommandId, 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 } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: IncreaseSearchEditorContextLinesCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, true), primary: KeyMod.Alt | KeyCode.US_EQUAL }); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DecreaseSearchEditorContextLinesCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), handler: (accessor: ServicesAccessor) => modifySearchEditorContextLinesCommand(accessor, false), primary: KeyMod.Alt | KeyCode.US_MINUS }); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: SelectAllSearchEditorMatchesCommandId, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, handler: selectAllSearchEditorMatchesCommand }); CommandsRegistry.registerCommand( CleanSearchEditorStateCommandId, (accessor: ServicesAccessor) => { const activeEditorPane = accessor.get(IEditorService).activeEditorPane; if (activeEditorPane instanceof SearchEditor) { activeEditorPane.cleanState(); } }); //#endregion //#region Actions const category = localize('search', "Search Editor"); export type OpenSearchEditorArgs = Partial; 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' }, triggerSearch: { type: 'boolean' }, focusResults: { type: 'boolean' }, } } }] } as const; registerAction2(class extends Action2 { constructor() { super({ id: SearchEditorConstants.OpenNewEditorCommandId, title: localize('search.openNewSearchEditor', "Open new Search Editor"), category, f1: true, description: openArgDescription }); } async run(accessor: ServicesAccessor, args: OpenSearchEditorArgs) { await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args); } }); registerAction2(class extends Action2 { constructor() { super({ id: OpenNewEditorToSideCommandId, title: localize('search.openNewEditorToSide', "Open new Search Editor to the Side"), category, f1: true, description: openArgDescription }); } async run(accessor: ServicesAccessor, args: OpenSearchEditorArgs) { await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, args, true); } }); registerAction2(class extends Action2 { constructor() { super({ id: OpenInEditorCommandId, title: localize('search.openResultsInEditor', "Open Results in Editor"), category, f1: true, keybinding: { primary: KeyMod.Alt | KeyCode.Enter, when: ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey), weight: KeybindingWeight.WorkbenchContrib, mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, }); } 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()); } } }); registerAction2(class extends Action2 { constructor() { super({ id: RerunSearchEditorSearchCommandId, title: localize('search.rerunSearchInEditor', "Search Again"), 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 }); } } }); registerAction2(class extends Action2 { constructor() { super({ id: FocusQueryEditorWidgetCommandId, title: localize('search.action.focusQueryEditorWidget', "Focus Search Editor Input"), 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(); } } }); //#endregion