diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 8f5b7d3e2a1..31446bfe4d9 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -142,6 +142,10 @@ "name": "vs/workbench/contrib/search", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/searchEditor", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/snippets", "project": "vscode-workbench" diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 901615aa5c4..27eea430aec 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -38,10 +38,11 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand, ReRunSearchEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; @@ -52,12 +53,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; -import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { SearchEditorInput, SearchEditorInputFactory, SearchEditorContribution } from 'vs/workbench/contrib/search/browser/searchEditorInput'; -import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import product from 'vs/platform/product/common/product'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -70,7 +68,7 @@ const category = nls.localize('search', "Search"); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.search.toggleQueryDetails', weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.or(Constants.SearchViewVisibleKey, Constants.InSearchEditor), + when: ContextKeyExpr.or(Constants.SearchViewVisibleKey, SearchEditorConstants.InSearchEditor), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_J, handler: accessor => { const editorService = accessor.get(IEditorService); @@ -205,7 +203,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: FocusNextInputAction.ID, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.or( - ContextKeyExpr.and(Constants.InSearchEditor, Constants.InputBoxFocusedKey), + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey)), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, handler: (accessor, args: any) => { @@ -217,7 +215,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: FocusPreviousInputAction.ID, weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.or( - ContextKeyExpr.and(Constants.InSearchEditor, Constants.InputBoxFocusedKey), + ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, Constants.InputBoxFocusedKey), ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated())), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, handler: (accessor, args: any) => { @@ -597,36 +595,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ handler: toggleRegexCommand }, ToggleRegexKeybinding)); -KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ - id: Constants.ToggleSearchEditorCaseSensitiveCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorCaseSensitiveCommand -}, ToggleCaseSensitiveKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ - id: Constants.ToggleSearchEditorWholeWordCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorWholeWordCommand -}, ToggleWholeWordKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ - id: Constants.ToggleSearchEditorRegexCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), - handler: toggleSearchEditorRegexCommand -}, ToggleRegexKeybinding)); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.ToggleSearchEditorContextLinesCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.InSearchEditor), - handler: toggleSearchEditorContextLinesCommand, - primary: KeyMod.Alt | KeyCode.KEY_L, - mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } -}); - KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.AddCursorsAtSearchResults, weight: KeybindingWeight.WorkbenchContrib, @@ -648,24 +616,6 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeA registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); - -registry.registerWorkbenchAction( - SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), - 'Search Editor: Rerun search', category, ContextKeyExpr.and(Constants.InSearchEditor) -); - -registry.registerWorkbenchAction( - SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, - { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, - ContextKeyExpr.and(Constants.HasSearchResults, Constants.SearchViewFocusedKey, Constants.EnableSearchEditorPreview)), - 'Search: Open Results in Editor', category, - ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); - -registry.registerWorkbenchAction( - SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), - 'Search: Open New Search Editor', category, - ContextKeyExpr.and(Constants.EnableSearchEditorPreview)); - // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( QuickOpenHandlerDescriptor.create( @@ -889,22 +839,3 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { }, order: 2 }); - - -Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - SearchEditor, - SearchEditor.ID, - nls.localize('defaultPreferencesEditor', "Search Editor") - ), - [ - new SyncDescriptor(SearchEditorInput) - ] -); - -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - SearchEditorInput.ID, - SearchEditorInputFactory); - -const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 36af97ce5fe..c730907976d 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -13,7 +13,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; @@ -22,13 +22,12 @@ import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfiguration, ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; -import { createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditorActions'; -import type { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; -import { SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorInput'; import { IViewsService } from 'vs/workbench/common/views'; +import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; export function isSearchViewFocused(viewsService: IViewsService): boolean { const searchView = getSearchView(viewsService); @@ -68,14 +67,6 @@ export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => { } }; -export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleCaseSensitive(); - } -}; - export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { @@ -83,14 +74,6 @@ export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { } }; -export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleWholeWords(); - } -}; - export const toggleRegexCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { @@ -98,22 +81,6 @@ export const toggleRegexCommand = (accessor: ServicesAccessor) => { } }; -export const toggleSearchEditorRegexCommand = (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleRegex(); - } -}; - -export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const input = editorService.activeEditor; - if (input instanceof SearchEditorInput) { - (editorService.activeControl as SearchEditor).toggleContextLines(); - } -}; - export class FocusNextInputAction extends Action { static readonly ID = 'search.focus.nextInputBox'; @@ -516,84 +483,6 @@ export class CancelSearchAction extends Action { } } -export class OpenSearchEditorAction extends Action { - - static readonly ID: string = Constants.OpenNewEditorCommandId; - static readonly LABEL = nls.localize('search.openNewEditor', "Open New Search Editor"); - - constructor(id: string, label: string, - @IConfigurationService private configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(id, label, 'codicon-new-file'); - } - - update() { - // pass - } - - get enabled(): boolean { - return true; - } - - async run() { - if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.instantiationService.invokeFunction(openNewSearchEditor); - } - } -} - -export class OpenResultsInEditorAction extends Action { - - static readonly ID: string = Constants.OpenInEditorCommandId; - static readonly LABEL = nls.localize('search.openResultsInEditor', "Open Results in Editor"); - - constructor(id: string, label: string, - @IViewsService private viewsService: IViewsService, - @IConfigurationService private configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(id, label, 'codicon-go-to-file'); - } - - get enabled(): boolean { - const searchView = getSearchView(this.viewsService); - return !!searchView && searchView.hasSearchResults(); - } - - update() { - this._setEnabled(this.enabled); - } - - async run() { - const searchView = getSearchView(this.viewsService); - if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); - } - } -} - - -export class ReRunSearchEditorSearchAction extends Action { - - static readonly ID = 'searchEditor.rerunSerach'; - static readonly LABEL = nls.localize('search.rerunSearch', "Rerun Search in Editor"); - - constructor(id: string, label: string, - @IEditorService private readonly editorService: IEditorService) { - super(id, label); - } - - async run() { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - await (this.editorService.activeControl as SearchEditor).runSearch(false, true); - } - } -} - - - export class FocusNextSearchResultAction extends Action { static readonly ID = 'search.action.focusNextSearchResult'; static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorActions.ts b/src/vs/workbench/contrib/search/browser/searchEditorActions.ts deleted file mode 100644 index e4e5c924bdc..00000000000 --- a/src/vs/workbench/contrib/search/browser/searchEditorActions.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { assertIsDefined } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import 'vs/css!./media/searchEditor'; -import { isDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { TrackedRangeStickiness } from 'vs/editor/common/model'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; -import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorInput'; -import { serializeSearchResultForEditor } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; - - -export const openNewSearchEditor = - async (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const telemetryService = accessor.get(ITelemetryService); - const instantiationService = accessor.get(IInstantiationService); - - const activeEditor = editorService.activeTextEditorWidget; - let activeModel: ICodeEditor | undefined; - let selected = ''; - if (activeEditor) { - if (isDiffEditor(activeEditor)) { - if (activeEditor.getOriginalEditor().hasTextFocus()) { - activeModel = activeEditor.getOriginalEditor(); - } else { - activeModel = activeEditor.getModifiedEditor(); - } - } else { - activeModel = activeEditor as ICodeEditor; - } - const selection = activeModel?.getSelection(); - selected = (selection && activeModel?.getModel()?.getValueInRange(selection)) ?? ''; - } else { - if (editorService.activeEditor instanceof SearchEditorInput) { - const active = editorService.activeControl as SearchEditor; - selected = active.getSelected(); - } - } - - telemetryService.publicLog2('searchEditor/openNewSearchEditor'); - - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected } }); - await editorService.openEditor(input, { pinned: true }); - }; - -export const createEditorFromSearchResult = - async (accessor: ServicesAccessor, searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string) => { - if (!searchResult.query) { - console.error('Expected searchResult.query to be defined. Got', searchResult); - return; - } - - const editorService = accessor.get(IEditorService); - const telemetryService = accessor.get(ITelemetryService); - const instantiationService = accessor.get(IInstantiationService); - const labelService = accessor.get(ILabelService); - - - telemetryService.publicLog2('searchEditor/createEditorFromSearchResult'); - - const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - - const { text, matchRanges } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); - - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text }); - const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; - const model = assertIsDefined(editor.getModel()); - model.deltaDecorations([], matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); - }; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 08ec6023f3e..8a8baf00872 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -43,7 +43,7 @@ import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/act import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IEditor } from 'vs/workbench/common/editor'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, OpenSearchEditorAction, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -63,9 +63,9 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; import { Selection } from 'vs/editor/common/core/selection'; -import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditorActions'; import { Color, RGBA } from 'vs/base/common/color'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { OpenSearchEditorAction, createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; const $ = dom.$; @@ -109,7 +109,6 @@ export class SearchView extends ViewPane { private folderMatchFocused: IContextKey; private matchFocused: IContextKey; private hasSearchResultsKey: IContextKey; - private enableSearchEditorPreview: IContextKey; private state: SearchUIState = SearchUIState.Idle; @@ -187,14 +186,7 @@ export class SearchView extends ViewPane { this.folderMatchFocused = Constants.FolderFocusKey.bindTo(contextKeyService); this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); - this.enableSearchEditorPreview = Constants.EnableSearchEditorPreview.bindTo(this.contextKeyService); - this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('search.previewSearchEditor')) { - this.enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - } - }); this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('search.sortOrder')) { if (this.searchConfig.sortOrder === SearchSortOrder.Modified) { diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 0e1cceb6b97..5ac1fa1a6fd 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -16,7 +16,6 @@ export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; export const OpenInEditorCommandId = 'search.action.openInEditor'; -export const OpenNewEditorCommandId = 'search.action.openNewEditor'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; export const FocusSearchListCommandID = 'search.action.focusSearchList'; export const ReplaceActionId = 'search.action.replace'; @@ -26,11 +25,6 @@ export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; export const ToggleRegexCommandId = 'toggleSearchRegex'; -export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; -export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; -export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; -export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; -export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; @@ -43,9 +37,6 @@ export const PatternIncludesFocusedKey = new RawContextKey('patternIncl export const PatternExcludesFocusedKey = new RawContextKey('patternExcludesInputBoxFocus', false); export const ReplaceActiveKey = new RawContextKey('replaceActive', false); export const HasSearchResults = new RawContextKey('hasSearchResult', false); -export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); -export const InSearchEditor = new RawContextKey('inSearchEditor', false); - export const FirstMatchFocusKey = new RawContextKey('firstMatchFocus', false); export const FileMatchOrMatchFocusKey = new RawContextKey('fileMatchOrMatchFocus', false); // This is actually, Match or File or Folder export const FileMatchOrFolderMatchFocusKey = new RawContextKey('fileMatchOrFolderMatchFocus', false); diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts new file mode 100644 index 00000000000..87d1491c003 --- /dev/null +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const OpenInEditorCommandId = 'search.action.openInEditor'; +export const OpenNewEditorCommandId = 'search.action.openNewEditor'; + +export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; +export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; +export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; +export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; + +export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); +export const InSearchEditor = new RawContextKey('inSearchEditor', false); + +export const SearchEditorScheme = 'search-editor'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/media/searchEditor.css b/src/vs/workbench/contrib/searchEditor/browser/media/searchEditor.css new file mode 100644 index 00000000000..c12e7737acc --- /dev/null +++ b/src/vs/workbench/contrib/searchEditor/browser/media/searchEditor.css @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.search-editor { + display: flex; + flex-direction: column; +} + +.search-editor .search-results { + flex: 1; +} + +.search-editor .query-container { + margin: 0px 12px 12px 2px; + padding-top: 6px; +} + +.search-editor .search-widget .toggle-replace-button { + position: absolute; + top: 0; + left: 0; + width: 16px; + height: 100%; + box-sizing: border-box; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.search-editor .search-widget .search-container, +.search-editor .search-widget .replace-container { + margin-left: 18px; + display: flex; + align-items: center; +} + +.search-editor .search-widget .monaco-findInput { + display: inline-block; + vertical-align: middle; + width: 100%; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper { + height: 100%; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror, +.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input { + padding: 3px; + padding-left: 4px; +} + +.search-editor .search-widget .monaco-inputbox > .wrapper > .mirror { + max-height: 134px; +} + +/* NOTE: height is also used in searchWidget.ts as a constant*/ +.search-editor .search-widget .monaco-inputbox > .wrapper > textarea.input { + overflow: initial; + height: 24px; /* set initial height before measure */ +} + +.search-editor .monaco-inputbox > .wrapper > textarea.input { + scrollbar-width: none; /* Firefox: hide scrollbar */ +} + +.search-editor .monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { + display: none; +} + +.search-editor .search-widget .context-lines-input { + display: none; +} + +.search-editor .search-widget.show-context .context-lines-input { + display: inherit; + margin-left: 5px; + margin-right: 2px; + max-width: 50px; +} + + +.search-editor .search-widget .replace-container { + margin-top: 6px; + position: relative; + display: inline-flex; +} + +.search-editor .search-widget .replace-input { + position: relative; + display: flex; + vertical-align: middle; + width: auto !important; + height: 25px; +} + +.search-editor .search-widget .replace-input > .controls { + position: absolute; + top: 3px; + right: 2px; +} + +.search-editor .search-widget .replace-container.disabled { + display: none; +} + +.search-editor .search-widget .replace-container .monaco-action-bar { + margin-left: 0; +} + +.search-editor .search-widget .replace-container .monaco-action-bar { + height: 25px; +} + +.search-editor .search-widget .replace-container .monaco-action-bar .action-item .codicon { + background-repeat: no-repeat; + width: 25px; + height: 25px; + margin-right: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.search-editor .includes-excludes { + min-height: 1em; + position: relative; + margin: 0 0 0 17px; +} + +.search-editor .includes-excludes .expand { + position: absolute; + right: -2px; + cursor: pointer; + width: 25px; + height: 16px; + z-index: 2; /* Force it above the search results message, which has a negative top margin */ +} + +.search-editor .includes-excludes .file-types { + display: none; +} + +.search-editor .includes-excludes.expanded .file-types { + display: inherit; +} + +.search-editor .includes-excludes.expanded .file-types:last-child { + padding-bottom: 10px; +} + +.search-editor .includes-excludes.expanded h4 { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding: 4px 0 0; + margin: 0; + font-size: 11px; + font-weight: normal; +} diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts new file mode 100644 index 00000000000..e5edd3ad998 --- /dev/null +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; +import { localize } from 'vs/nls'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +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 { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry, IEditorInputFactory } from 'vs/workbench/common/editor'; +import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; +import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, ReRunSearchEditorSearchAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { SearchEditorInput, getOrMakeSearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { endsWith } from 'vs/base/common/strings'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { URI } from 'vs/base/common/uri'; + +//#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, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + const enableSearchEditorPreview = SearchEditorConstants.EnableSearchEditorPreview.bindTo(this.contextKeyService); + + enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('search.previewSearchEditor')) { + enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); + } + }); + + this.editorService.overrideOpenEditor((editor, options, group) => { + const resource = editor.getResource(); + if (!resource || + !(endsWith(resource.path, '.code-search') || resource.scheme === SearchEditorConstants.SearchEditorScheme) || + !(editor instanceof FileEditorInput || (resource.scheme === SearchEditorConstants.SearchEditorScheme))) { + return undefined; + } + + if (group.isOpened(editor)) { + return undefined; + } + + this.telemetryService.publicLog2('searchEditor/openSavedSearchEditor'); + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: resource }); + const opened = editorService.openEditor(input, { ...options, pinned: resource.scheme === SearchEditorConstants.SearchEditorScheme, ignoreOverrides: true }, group); + return { override: Promise.resolve(opened) }; + }); + } + + private get searchConfig(): ISearchConfigurationProperties { + return this.configurationService.getValue('search'); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); +//#endregion + +//#region Input Factory +class SearchEditorInputFactory implements IEditorInputFactory { + + canSerialize() { return true; } + + serialize(input: SearchEditorInput) { + let resource = undefined; + if (input.resource.path || input.resource.fragment) { + resource = input.resource.toString(); + } + + const config = input.getConfigSync(); + const dirty = input.isDirty(); + const highlights = input.highlights; + + return JSON.stringify({ resource, dirty, config, viewState: input.viewState, name: input.getName(), highlights }); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { + const { resource, dirty, config, viewState, highlights } = JSON.parse(serializedEditorInput); + if (config && (config.query !== undefined)) { + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config, uri: URI.parse(resource) }); + input.viewState = viewState; + input.setDirty(dirty); + input.setHighlights(highlights); + return input; + } + return undefined; + } +} + +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( + SearchEditorInput.ID, + SearchEditorInputFactory); +//#endregion + +//#region Commands +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: SearchEditorConstants.ToggleSearchEditorCaseSensitiveCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorCaseSensitiveCommand +}, ToggleCaseSensitiveKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: SearchEditorConstants.ToggleSearchEditorWholeWordCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorWholeWordCommand +}, ToggleWholeWordKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: SearchEditorConstants.ToggleSearchEditorRegexCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(SearchEditorConstants.InSearchEditor, SearchConstants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorRegexCommand +}, ToggleRegexKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SearchEditorConstants.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 } +}); +//#endregion + +//#region Actions +const registry = Registry.as(ActionExtensions.WorkbenchActions); +const category = localize('search', "Search Editor"); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), + 'Search Editor: Rerun search', category, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor) +); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, + { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, + ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey, SearchEditorConstants.EnableSearchEditorPreview)), + 'Search: Open Results in Editor', category, + ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), + 'Search: Open New Search Editor', category, + ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); +//#endregion diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts similarity index 96% rename from src/vs/workbench/contrib/search/browser/searchEditor.ts rename to src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index ebf91c05853..39d6af1af30 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -34,14 +34,15 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; import { Delayer } from 'vs/base/common/async'; -import { serializeSearchResultForEditor } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; +import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { InSearchEditor, InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; +import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; -import type { SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorInput'; +import type { SearchEditorInput, SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { searchEditorFindMatchBorder, searchEditorFindMatch, registerColor, inputBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { ReferencesController } from 'vs/editor/contrib/gotoSymbol/peek/referencesController'; +import { InSearchEditor } from 'vs/workbench/contrib/searchEditor/browser/constants'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const FILE_LINE_REGEX = /^(\S.*):$/; @@ -189,8 +190,7 @@ export class SearchEditor extends BaseEditor { this._register(this.searchResultEditor.onKeyDown(e => e.keyCode === KeyCode.Escape && this.queryEditorWidget.searchInput.focus())); - this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); - this._register(this.searchResultEditor.onDidChangeModelContent(() => (this._input as SearchEditorInput)?.setDirty(true))); + this._register(this.searchResultEditor.onDidChangeModelContent(() => this.getInput()?.setDirty(true))); [this.queryEditorWidget.searchInputFocusTracker, this.queryEditorWidget.replaceInputFocusTracker, this.inputPatternExcludes.inputFocusTracker, this.inputPatternIncludes.inputFocusTracker] .map(tracker => { @@ -338,7 +338,6 @@ export class SearchEditor extends BaseEditor { const textModel = assertIsDefined(this.searchResultEditor.getModel()); this.modelService.updateModel(textModel, results.text); this.getInput()?.setDirty(this.getInput()?.resource.scheme !== 'search-editor'); - this.hideHeader(); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); (assertIsDefined(this._input) as SearchEditorInput).reloadModel(); @@ -346,23 +345,6 @@ export class SearchEditor extends BaseEditor { searchModel.dispose(); } - private hideHeader() { - const headerLines = - (this.searchResultEditor - .getModel() - ?.getValueInRange(new Range(1, 1, 6, 1)) - .split('\n') - .filter(line => line.startsWith('#')) - .length - ?? 0) + 1; - - if (headerLines !== this.searchResultEditor.getModel()?.getLineCount()) { - this.searchResultEditor.setHiddenAreas([new Range(1, 1, headerLines, 1)]); - } else { - this.searchResultEditor.setHiddenAreas([new Range(1, 1, headerLines - 1, 1)]); - } - } - layout(dimension: DOM.Dimension) { this.dimension = dimension; this.reLayout(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts new file mode 100644 index 00000000000..decaf2a43aa --- /dev/null +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import { assertIsDefined } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import 'vs/css!./media/searchEditor'; +import { ICodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { TrackedRangeStickiness } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IViewsService } from 'vs/workbench/common/views'; +import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import * as Constants from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; + +export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleCaseSensitive(); + } +}; + +export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleWholeWords(); + } +}; + +export const toggleSearchEditorRegexCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleRegex(); + } +}; + +export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleContextLines(); + } +}; + + +export class OpenSearchEditorAction extends Action { + + static readonly ID: string = Constants.OpenNewEditorCommandId; + static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor"); + + constructor(id: string, label: string, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(id, label, 'codicon-new-file'); + } + + update() { + // pass + } + + get enabled(): boolean { + return true; + } + + async run() { + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.instantiationService.invokeFunction(openNewSearchEditor); + } + } +} + +export class OpenResultsInEditorAction extends Action { + + static readonly ID: string = Constants.OpenInEditorCommandId; + static readonly LABEL = localize('search.openResultsInEditor', "Open Results in Editor"); + + constructor(id: string, label: string, + @IViewsService private viewsService: IViewsService, + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(id, label, 'codicon-go-to-file'); + } + + get enabled(): boolean { + const searchView = getSearchView(this.viewsService); + return !!searchView && searchView.hasSearchResults(); + } + + update() { + this._setEnabled(this.enabled); + } + + async run() { + const searchView = getSearchView(this.viewsService); + if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); + } + } +} + + +export class ReRunSearchEditorSearchAction extends Action { + + static readonly ID = 'searchEditor.rerunSerach'; + static readonly LABEL = localize('search.rerunSearch', "Rerun Search in Editor"); + + constructor(id: string, label: string, + @IEditorService private readonly editorService: IEditorService) { + super(id, label); + } + + async run() { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + await (this.editorService.activeControl as SearchEditor).runSearch(false, true); + } + } +} + +const openNewSearchEditor = + async (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const telemetryService = accessor.get(ITelemetryService); + const instantiationService = accessor.get(IInstantiationService); + + const activeEditor = editorService.activeTextEditorWidget; + let activeModel: ICodeEditor | undefined; + let selected = ''; + if (activeEditor) { + if (isDiffEditor(activeEditor)) { + if (activeEditor.getOriginalEditor().hasTextFocus()) { + activeModel = activeEditor.getOriginalEditor(); + } else { + activeModel = activeEditor.getModifiedEditor(); + } + } else { + activeModel = activeEditor as ICodeEditor; + } + const selection = activeModel?.getSelection(); + selected = (selection && activeModel?.getModel()?.getValueInRange(selection)) ?? ''; + } else { + if (editorService.activeEditor instanceof SearchEditorInput) { + const active = editorService.activeControl as SearchEditor; + selected = active.getSelected(); + } + } + + telemetryService.publicLog2('searchEditor/openNewSearchEditor'); + + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: { query: selected } }); + await editorService.openEditor(input, { pinned: true }); + }; + +export const createEditorFromSearchResult = + async (accessor: ServicesAccessor, searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string) => { + if (!searchResult.query) { + console.error('Expected searchResult.query to be defined. Got', searchResult); + return; + } + + const editorService = accessor.get(IEditorService); + const telemetryService = accessor.get(ITelemetryService); + const instantiationService = accessor.get(IInstantiationService); + const labelService = accessor.get(ILabelService); + + + telemetryService.publicLog2('searchEditor/createEditorFromSearchResult'); + + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); + + const { text, matchRanges } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); + + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text }); + const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; + const model = assertIsDefined(editor.getModel()); + model.deltaDecorations([], matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); + }; diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts similarity index 76% rename from src/vs/workbench/contrib/search/browser/searchEditorInput.ts rename to src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 76542852994..90d61376d9b 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -3,32 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter, Event } from 'vs/base/common/event'; import * as network from 'vs/base/common/network'; -import { endsWith } from 'vs/base/common/strings'; +import { basename } from 'vs/base/common/path'; +import { isEqual, joinPath, toLocalResource } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; -import { ITextModel, ITextBufferFactory, IModelDeltaDecoration } from 'vs/editor/common/model'; -import { localize } from 'vs/nls'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorInputFactory, GroupIdentifier, EditorInput, IRevertOptions, ISaveOptions, IEditorInput } from 'vs/workbench/common/editor'; +import type { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { joinPath, isEqual, toLocalResource } from 'vs/base/common/resources'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { localize } from 'vs/nls'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { basename } from 'vs/base/common/path'; -import { IWorkingCopyService, WorkingCopyCapabilities, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; -import type { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { Emitter, Event } from 'vs/base/common/event'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { EditorInput, GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { SearchEditorScheme } from 'vs/workbench/contrib/searchEditor/browser/constants'; export type SearchConfiguration = { query: string, @@ -46,8 +44,6 @@ type SearchEditorViewState = | { focused: 'input' } | { focused: 'editor', state: ICodeEditorViewState }; -const searchEditorScheme = 'search-editor'; - export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; @@ -202,7 +198,7 @@ export class SearchEditorInput extends EditorInput { } isUntitled() { - return this.resource.scheme === searchEditorScheme; + return this.resource.scheme === SearchEditorScheme; } dispose() { @@ -266,68 +262,6 @@ export class SearchEditorInput extends EditorInput { } } - - -export class SearchEditorContribution implements IWorkbenchContribution { - constructor( - @IEditorService private readonly editorService: IEditorService, - @ITextFileService protected readonly textFileService: ITextFileService, - @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModelService protected readonly modelService: IModelService, - @ITelemetryService protected readonly telemetryService: ITelemetryService, - ) { - - this.editorService.overrideOpenEditor((editor, options, group) => { - const resource = editor.getResource(); - if (!resource || - !(endsWith(resource.path, '.code-search') || resource.scheme === searchEditorScheme) || - !(editor instanceof FileEditorInput || (resource.scheme === searchEditorScheme))) { - return undefined; - } - - if (group.isOpened(editor)) { - return undefined; - } - - this.telemetryService.publicLog2('searchEditor/openSavedSearchEditor'); - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: resource }); - const opened = editorService.openEditor(input, { ...options, pinned: resource.scheme === searchEditorScheme, ignoreOverrides: true }, group); - return { override: Promise.resolve(opened) }; - }); - } -} - -export class SearchEditorInputFactory implements IEditorInputFactory { - - canSerialize() { return true; } - - serialize(input: SearchEditorInput) { - let resource = undefined; - if (input.resource.path || input.resource.fragment) { - resource = input.resource.toString(); - } - - const config = input.getConfigSync(); - const dirty = input.isDirty(); - const highlights = input.highlights; - - return JSON.stringify({ resource, dirty, config, viewState: input.viewState, name: input.getName(), highlights }); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - const { resource, dirty, config, viewState, highlights } = JSON.parse(serializedEditorInput); - if (config && (config.query !== undefined)) { - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config, uri: URI.parse(resource) }); - input.viewState = viewState; - input.setDirty(dirty); - input.setHighlights(highlights); - return input; - } - return undefined; - } -} - - const inputs = new Map(); export const getOrMakeSearchEditorInput = ( accessor: ServicesAccessor, @@ -337,7 +271,7 @@ export const getOrMakeSearchEditorInput = ( { config: Partial, text?: never, uri?: never } ): SearchEditorInput => { - const uri = existingData.uri ?? URI.from({ scheme: searchEditorScheme, fragment: `${Math.random()}` }); + const uri = existingData.uri ?? URI.from({ scheme: SearchEditorScheme, fragment: `${Math.random()}` }); const instantiationService = accessor.get(IInstantiationService); const modelService = accessor.get(IModelService); @@ -363,7 +297,7 @@ export const getOrMakeSearchEditorInput = ( if (backup) { contents = backup.value; - } else if (uri.scheme !== searchEditorScheme) { + } else if (uri.scheme !== SearchEditorScheme) { contents = (await textFileService.read(uri)).value; } else if (existingData.text) { contents = existingData.text; diff --git a/src/vs/workbench/contrib/search/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts similarity index 99% rename from src/vs/workbench/contrib/search/browser/searchEditorSerialization.ts rename to src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index c3ef3a26700..e55670d8af0 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -12,7 +12,7 @@ import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbenc import { ITextQuery } from 'vs/workbench/services/search/common/search'; import { localize } from 'vs/nls'; import type { ITextModel } from 'vs/editor/common/model'; -import type { SearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorInput'; +import type { SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 83a7e09f4ba..7a4159c2e8f 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -161,6 +161,9 @@ import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView'; import 'vs/workbench/contrib/search/browser/openAnythingHandler'; +// Search Editor +import 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; + // SCM import 'vs/workbench/contrib/scm/browser/scm.contribution'; import 'vs/workbench/contrib/scm/browser/scmViewlet';