From 56a6279a1c8616781180bfa52b4b5fb3a1ac243b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 14 Jan 2021 14:52:59 -0800 Subject: [PATCH] Don't use getActions in search view #92038 --- src/vs/base/browser/ui/findinput/findInput.ts | 4 + .../search/browser/search.contribution.ts | 196 ++++++++---- .../contrib/search/browser/searchActions.ts | 278 ++++-------------- .../contrib/search/browser/searchView.ts | 206 ++++++------- .../contrib/search/common/constants.ts | 5 +- .../workbench/contrib/search/common/search.ts | 9 + .../browser/searchEditor.contribution.ts | 28 +- .../browser/searchEditorActions.ts | 77 ++--- 8 files changed, 361 insertions(+), 442 deletions(-) diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index cf7db18c20b..e0e21cad1e9 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -258,6 +258,10 @@ export class FindInput extends Widget { this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e)); } + public get onDidChange(): Event { + return this.inputBox.onDidChange; + } + public enable(): void { this.domNode.classList.remove('disabled'); this.inputBox.enable(); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 8834497c4ff..ebda880f066 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -8,53 +8,53 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; +import { assertIsDefined, assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; +import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; import * as nls from 'vs/nls'; -import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Action2, ICommandAction, MenuId, MenuRegistry, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { Extensions as QuickAccessExtensions, IQuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { defaultQuickAccessContextKeyValue } from 'vs/workbench/browser/quickaccess'; import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { Extensions as ViewExtensions, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; +import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { getMultiSelectedResources, IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; +import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; 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, togglePreserveCaseCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { cancelSearch, clearHistoryCommand, clearSearchResults, CloseReplaceAction, collapseDeepestExpandedLevel, copyAllCommand, copyMatchCommand, copyPathCommand, expandAll, FindInFilesCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, refreshSearch, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, togglePreserveCaseCommand, toggleRegexCommand, ToggleSearchOnTypeAction, toggleWholeWordCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchStopIcon, searchViewIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; +import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; 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 { resolveResourcesForSearchIncludes } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { getWorkspaceSymbols, SearchStateKey, SearchUIState } 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'; +import { FileMatch, FileMatchOrMatch, FolderMatch, ISearchWorkbenchService, Match, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; +import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ISearchConfiguration, SearchSortOrder, SEARCH_EXCLUDE_CONFIG, VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { assertType, assertIsDefined } from 'vs/base/common/types'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; -import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; -import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; -import { GotoSymbolQuickAccessProvider } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; -import { searchViewIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; -import { resolveResourcesForSearchIncludes } from 'vs/workbench/contrib/search/common/queryBuilder'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -128,19 +128,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: Constants.CancelActionId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), - primary: KeyCode.Escape, - handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewsService)); - if (searchView) { - searchView.cancelSearch(); - } - } -}); - KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.RemoveActionId, weight: KeybindingWeight.WorkbenchContrib, @@ -373,6 +360,121 @@ CommandsRegistry.registerCommand({ } }); +registerAction2(class CancelSearchAction extends Action2 { + constructor() { + super({ + id: 'search.action.cancel', + title: nls.localize('CancelSearchAction.label', "Cancel Search"), + icon: searchStopIcon, + category, + f1: true, + precondition: SearchStateKey.isEqualTo(SearchUIState.Idle).negate(), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), + primary: KeyCode.Escape, + }, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 0, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch)), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return cancelSearch(accessor); + } +}); + +registerAction2(class RefreshAction extends Action2 { + constructor() { + super({ + id: 'search.action.refreshSearchResults', + title: nls.localize('RefreshAction.label', "Refresh"), + icon: searchRefreshIcon, + precondition: Constants.ViewHasSearchPatternKey, + category, + f1: true, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 0, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', VIEW_ID), SearchStateKey.isEqualTo(SearchUIState.SlowSearch).negate()), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return refreshSearch(accessor); + } +}); + +registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { + constructor() { + super({ + id: 'search.action.collapseSearchResults', + title: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + category, + icon: searchCollapseAllIcon, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', VIEW_ID), ContextKeyExpr.or(Constants.HasSearchResults.negate(), Constants.ViewHasSomeCollapsibleKey)), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return collapseDeepestExpandedLevel(accessor); + } +}); + +registerAction2(class ExpandAllAction extends Action2 { + constructor() { + super({ + id: 'search.action.expandSearchResults', + title: nls.localize('ExpandAllAction.label', "Expand All"), + category, + icon: searchExpandAllIcon, + f1: true, + precondition: ContextKeyExpr.and(Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 3, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', VIEW_ID), Constants.HasSearchResults, Constants.ViewHasSomeCollapsibleKey.toNegated()), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return expandAll(accessor); + } +}); + +registerAction2(class ClearSearchResultsAction extends Action2 { + constructor() { + super({ + id: 'search.action.clearSearchResults', + title: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + category, + icon: searchClearIcon, + f1: true, + precondition: ContextKeyExpr.or(Constants.HasSearchResults, Constants.ViewHasSearchPatternKey, Constants.ViewHasReplacePatternKey, Constants.ViewHasFilePatternKey), + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 1, + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return clearSearchResults(accessor); + } +}); + const RevealInSideBarForSearchResultsCommand: ICommandAction = { id: Constants.RevealInSideBarForSearchResults, title: nls.localize('revealInSideBar', "Reveal in Side Bar") @@ -449,20 +551,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: searchInFolderCommand }); -CommandsRegistry.registerCommand({ - id: ClearSearchResultsAction.ID, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, '').run(); - } -}); - -CommandsRegistry.registerCommand({ - id: RefreshAction.ID, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(RefreshAction, RefreshAction.ID, '').run(); - } -}); - const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace'; CommandsRegistry.registerCommand({ id: FIND_IN_WORKSPACE_ID, @@ -686,12 +774,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -registry.registerWorkbenchAction(SyncActionDescriptor.from(CollapseDeepestExpandedLevelAction), 'Search: Collapse All', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ExpandAllAction), 'Search: Expand All', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowAllSymbolsAction, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSearchOnTypeAction), 'Search: Toggle Search on Type', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(RefreshAction), 'Search: Refresh', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ClearSearchResultsAction), 'Search: Clear Search Results', category.value); // Register Quick Access Handler const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 00bdbc81b0e..d0e84ccc97f 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -4,34 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { isWindows, OS } from 'vs/base/common/platform'; import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILabelService } from 'vs/platform/label/common/label'; import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IViewsService } from 'vs/workbench/common/views'; +import { searchRemoveIcon, searchReplaceAllIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { FolderMatch, FileMatch, 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, VIEW_ID, VIEWLET_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 { IViewsService } from 'vs/workbench/common/views'; -import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { OpenEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { searchRefreshIcon, searchCollapseAllIcon, searchExpandAllIcon, searchClearIcon, searchReplaceAllIcon, searchReplaceIcon, searchRemoveIcon, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; -import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; -import { OpenEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; +import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ISearchConfiguration, VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; export function isSearchViewFocused(viewsService: IViewsService): boolean { const searchView = getSearchView(viewsService); @@ -289,227 +289,77 @@ export class ToggleSearchOnTypeAction extends Action { } } - -export class RefreshAction extends Action { - - static readonly ID: string = 'search.action.refreshSearchResults'; - static LABEL: string = nls.localize('RefreshAction.label', "Refresh"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'search-action ' + ThemeIcon.asClassName(searchRefreshIcon)); - } - - get enabled(): boolean { - const searchView = getSearchView(this.viewsService); - return !!searchView && searchView.hasSearchPattern(); +export function expandAll(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + const viewer = searchView.getControl(); + viewer.expandAll(); + viewer.domFocus(); + viewer.focusFirst(); } +} - update(): void { - this._setEnabled(this.enabled); +export function clearSearchResults(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + searchView.clearSearchResults(); } +} - run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - searchView.triggerQueryChange({ preserveFocus: false }); - } - - return Promise.resolve(); +export function cancelSearch(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + searchView.cancelSearch(); } } -export class CollapseDeepestExpandedLevelAction extends Action { - - static readonly ID: string = 'search.action.collapseSearchResults'; - static LABEL: string = nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'search-action ' + ThemeIcon.asClassName(searchCollapseAllIcon)); - this.update(); +export function refreshSearch(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + searchView.triggerQueryChange({ preserveFocus: false }); } +} - update(): void { - const searchView = getSearchView(this.viewsService); - this.enabled = !!searchView && searchView.hasSearchResults(); - } +export function collapseDeepestExpandedLevel(accessor: ServicesAccessor) { - run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - const viewer = searchView.getControl(); - - /** - * one level to collapse so collapse everything. If FolderMatch, check if there are visible grandchildren, - * i.e. if Matches are returned by the navigator, and if so, collapse to them, otherwise collapse all levels. - */ - const navigator = viewer.navigate(); - let node = navigator.first(); - let collapseFileMatchLevel = false; - if (node instanceof FolderMatch) { - while (node = navigator.next()) { - if (node instanceof Match) { - collapseFileMatchLevel = true; - break; - } + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + const viewer = searchView.getControl(); + + /** + * one level to collapse so collapse everything. If FolderMatch, check if there are visible grandchildren, + * i.e. if Matches are returned by the navigator, and if so, collapse to them, otherwise collapse all levels. + */ + const navigator = viewer.navigate(); + let node = navigator.first(); + let collapseFileMatchLevel = false; + if (node instanceof FolderMatch) { + while (node = navigator.next()) { + if (node instanceof Match) { + collapseFileMatchLevel = true; + break; } } - - if (collapseFileMatchLevel) { - node = navigator.first(); - do { - if (node instanceof FileMatch) { - viewer.collapse(node); - } - } while (node = navigator.next()); - } else { - viewer.collapseAll(); - } - - viewer.domFocus(); - viewer.focusFirst(); - } - return Promise.resolve(undefined); - } -} - -export class ExpandAllAction extends Action { - - static readonly ID: string = 'search.action.expandSearchResults'; - static LABEL: string = nls.localize('ExpandAllAction.label', "Expand All"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'search-action ' + ThemeIcon.asClassName(searchExpandAllIcon)); - this.update(); - } - - update(): void { - const searchView = getSearchView(this.viewsService); - this.enabled = !!searchView && searchView.hasSearchResults(); - } - - run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - const viewer = searchView.getControl(); - viewer.expandAll(); - viewer.domFocus(); - viewer.focusFirst(); } - return Promise.resolve(undefined); - } -} - -export class ToggleCollapseAndExpandAction extends Action { - static readonly ID: string = 'search.action.collapseOrExpandSearchResults'; - static LABEL: string = nls.localize('ToggleCollapseAndExpandAction.label', "Toggle Collapse and Expand"); - // Cache to keep from crawling the tree too often. - private action: CollapseDeepestExpandedLevelAction | ExpandAllAction | undefined; - - constructor(id: string, label: string, - private collapseAction: CollapseDeepestExpandedLevelAction, - private expandAction: ExpandAllAction, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, collapseAction.class); - this.update(); - } - - update(): void { - const searchView = getSearchView(this.viewsService); - this.enabled = !!searchView && searchView.hasSearchResults(); - this.onTreeCollapseStateChange(); - } - - onTreeCollapseStateChange() { - this.action = undefined; - this.determineAction(); - } - - private determineAction(): CollapseDeepestExpandedLevelAction | ExpandAllAction { - if (this.action !== undefined) { return this.action; } - this.action = this.isSomeCollapsible() ? this.collapseAction : this.expandAction; - this.class = this.action.class; - return this.action; - } - - private isSomeCollapsible(): boolean { - const searchView = getSearchView(this.viewsService); - if (searchView) { - const viewer = searchView.getControl(); - const navigator = viewer.navigate(); - let node = navigator.first(); + if (collapseFileMatchLevel) { + node = navigator.first(); do { - if (!viewer.isCollapsed(node)) { - return true; + if (node instanceof FileMatch) { + viewer.collapse(node); } } while (node = navigator.next()); - } - return false; - } - - - async run(): Promise { - await this.determineAction().run(); - } -} - -export class ClearSearchResultsAction extends Action { - - static readonly ID: string = 'search.action.clearSearchResults'; - static LABEL: string = nls.localize('ClearSearchResultsAction.label', "Clear Search Results"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'search-action ' + ThemeIcon.asClassName(searchClearIcon)); - this.update(); - } - - update(): void { - const searchView = getSearchView(this.viewsService); - this.enabled = !!searchView && (!searchView.allSearchFieldsClear() || searchView.hasSearchResults() || !searchView.allFilePatternFieldsClear()); - } - - run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - searchView.clearSearchResults(); - } - return Promise.resolve(); - } -} - -export class CancelSearchAction extends Action { - - static readonly ID: string = 'search.action.cancelSearch'; - static LABEL: string = nls.localize('CancelSearchAction.label', "Cancel Search"); - - constructor(id: string, label: string, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, 'search-action ' + ThemeIcon.asClassName(searchStopIcon)); - this.update(); - } - - update(): void { - const searchView = getSearchView(this.viewsService); - this.enabled = !!searchView && searchView.isSlowSearch(); - } - - run(): Promise { - const searchView = getSearchView(this.viewsService); - if (searchView) { - searchView.cancelSearch(); + } else { + viewer.collapseAll(); } - return Promise.resolve(undefined); + viewer.domFocus(); + viewer.focusFirst(); } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index c1c16e601a1..4546b6151ab 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -8,9 +8,11 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; +import { Color, RGBA } from 'vs/base/common/color'; import * as errors from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -20,10 +22,13 @@ import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchview'; -import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { getCodeEditor, ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Selection } from 'vs/editor/common/core/selection'; import { CommonFindController } from 'vs/editor/contrib/find/findController'; +import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; import * as nls from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -32,53 +37,42 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; -import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; -import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, foreground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ResourceLabels } from 'vs/workbench/browser/labels'; +import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IEditorPane } from 'vs/workbench/common/editor'; +import { Memento, MementoObject } from 'vs/workbench/common/memento'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction, IFindInFilesArgs, appendKeyBindingLabel, ExpandAllAction, ToggleCollapseAndExpandAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; +import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; 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'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; -import { FileMatch, FileMatchOrMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult, FolderMatch, FolderMatchWithResource } from 'vs/workbench/contrib/search/common/searchModel'; +import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; +import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -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 { Color, RGBA } from 'vs/base/common/color'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { OpenSearchEditorAction, createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Orientation } from 'vs/base/browser/ui/sash/sash'; -import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; const $ = dom.$; -enum SearchUIState { - Idle, - Searching, - SlowSearch -} - export enum SearchViewPosition { SideBar, Panel @@ -112,12 +106,12 @@ export class SearchView extends ViewPane { private hasSearchResultsKey: IContextKey; private lastFocusState: 'input' | 'tree' = 'input'; - private state: SearchUIState = SearchUIState.Idle; + private searchStateKey: IContextKey; + private hasSearchPatternKey: IContextKey; + private hasReplacePatternKey: IContextKey; + private hasFilePatternKey: IContextKey; + private hasSomeCollapsibleResultKey: IContextKey; - private actions: Array = []; - private toggleCollapseAction: ToggleCollapseAndExpandAction; - private cancelAction: CancelSearchAction; - private refreshAction: RefreshAction; private contextMenu: IMenu | null = null; private tree!: WorkbenchObjectTree; @@ -138,7 +132,6 @@ export class SearchView extends ViewPane { private delayedRefresh: Delayer; private changedWhileHidden: boolean = false; - private updatedActionsWhileHidden = false; private searchWithoutFolderMessageElement: HTMLElement | undefined; @@ -194,6 +187,11 @@ export class SearchView extends ViewPane { this.folderMatchFocused = Constants.FolderFocusKey.bindTo(this.contextKeyService); this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); this.matchFocused = Constants.MatchFocusKey.bindTo(this.contextKeyService); + this.searchStateKey = SearchStateKey.bindTo(this.contextKeyService); + this.hasSearchPatternKey = Constants.ViewHasSearchPatternKey.bindTo(this.contextKeyService); + this.hasReplacePatternKey = Constants.ViewHasReplacePatternKey.bindTo(this.contextKeyService); + this.hasFilePatternKey = Constants.ViewHasFilePatternKey.bindTo(this.contextKeyService); + this.hasSomeCollapsibleResultKey = Constants.ViewHasSomeCollapsibleKey.bindTo(this.contextKeyService); // scoped this.contextKeyService = this._register(this.contextKeyService.createScoped(this.container)); @@ -232,19 +230,22 @@ export class SearchView extends ViewPane { this.toggleCollapseStateDelayer = this._register(new Delayer(100)); this.triggerQueryDelayer = this._register(new Delayer(0)); - const collapseDeepestExpandedLevelAction = this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL); - const expandAllAction = this.instantiationService.createInstance(ExpandAllAction, ExpandAllAction.ID, ExpandAllAction.LABEL); + this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel); + } - this.actions = [ - this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), - this._register(this.instantiationService.createInstance(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL)) - ]; + private get state(): SearchUIState { + return this.searchStateKey.get() ?? SearchUIState.Idle; + } - this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); - this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); - this.toggleCollapseAction = this._register(this.instantiationService.createInstance(ToggleCollapseAndExpandAction, ToggleCollapseAndExpandAction.ID, ToggleCollapseAndExpandAction.LABEL, collapseDeepestExpandedLevelAction, expandAllAction)); + private set state(v: SearchUIState) { + this.searchStateKey.set(v); + } - this.treeAccessibilityProvider = this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel); + /** + * Exposed for openSearchEditor TODO@JacksonKearl + */ + getInstantiationService(): IInstantiationService { + return this.instantiationService; } getContainer(): HTMLElement { @@ -322,8 +323,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes.setValue(patternIncludes); - this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); - this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)); + this._register(this.inputPatternIncludes.onCancel(() => this.cancelSearch(false))); this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); // excludes list @@ -338,11 +338,21 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(patternExclusions); this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles); - this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })); - this.inputPatternExcludes.onCancel(() => this.cancelSearch(false)); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerQueryChange()); + this._register(this.inputPatternExcludes.onCancel(() => this.cancelSearch(false))); + this._register(this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerQueryChange())); this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); + const updateHasFilePatternKey = () => this.hasFilePatternKey.set(this.inputPatternIncludes.getValue().length > 0 || this.inputPatternExcludes.getValue().length > 0); + updateHasFilePatternKey(); + const onFilePatternSubmit = (triggeredOnType: boolean) => { + this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod }); + if (triggeredOnType) { + updateHasFilePatternKey(); + } + }; + this._register(this.inputPatternIncludes.onSubmit(onFilePatternSubmit)); + this._register(this.inputPatternExcludes.onSubmit(onFilePatternSubmit)); + this.messagesElement = dom.append(this.container, $('.messages')); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); @@ -356,9 +366,6 @@ export class SearchView extends ViewPane { this._register(this.viewModel.searchResult.onChange((event) => this.onSearchResultsChanged(event))); - this._register(this.searchWidget.searchInput.onInput(() => this.updateActions())); - this._register(this.searchWidget.replaceInput.onInput(() => this.updateActions())); - this._register(this.onDidChangeBodyVisibility(visible => this.onVisibilityChanged(visible))); } @@ -370,13 +377,6 @@ export class SearchView extends ViewPane { this.refreshAndUpdateCount(); this.changedWhileHidden = false; } - - if (this.updatedActionsWhileHidden) { - // The actions can only run or update their enablement when the view is visible, - // because they can only access the view when it's visible - this.updateActions(); - this.updatedActionsWhileHidden = false; - } } else { // Reset last focus to input to preserve opening the viewlet always focusing the query editor. this.lastFocusState = 'input'; @@ -400,25 +400,6 @@ export class SearchView extends ViewPane { return this.inputPatternExcludes; } - /** - * Warning: a bit expensive due to updating the view title - */ - protected updateActions(): void { - if (!this.isVisible()) { - this.updatedActionsWhileHidden = true; - } - - for (const action of this.actions) { - action.update(); - } - - this.refreshAction.update(); - this.cancelAction.update(); - this.toggleCollapseAction.update(); - - super.updateActions(); - } - private createSearchWidget(container: HTMLElement): void { const contentPattern = this.viewletState['query.contentPattern'] || ''; const replaceText = this.viewletState['query.replaceText'] || ''; @@ -450,6 +431,14 @@ export class SearchView extends ViewPane { this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus))); this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.triggerQueryChange())); + const updateHasPatternKey = () => this.hasSearchPatternKey.set(this.searchWidget.searchInput.getValue().length > 0); + updateHasPatternKey(); + this._register(this.searchWidget.searchInput.onDidChange(() => updateHasPatternKey())); + + const updateHasReplacePatternKey = () => this.hasReplacePatternKey.set(this.searchWidget.getReplaceValue().length > 0); + updateHasReplacePatternKey(); + this._register(this.searchWidget.replaceInput.inputBox.onDidChange(() => updateHasReplacePatternKey())); + this._register(this.searchWidget.onDidHeightChange(() => this.reLayout())); this._register(this.searchWidget.onReplaceToggled(() => this.reLayout())); @@ -729,9 +718,10 @@ export class SearchView extends ViewPane { } })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); - this._register(this.tree.onDidChangeCollapseState(() => - this.toggleCollapseStateDelayer.trigger(() => this.toggleCollapseAction.onTreeCollapseStateChange()) - )); + const updateHasSomeCollapsible = () => this.toggleCollapseStateDelayer.trigger(() => this.hasSomeCollapsibleResultKey.set(this.hasSomeCollapsible())); + updateHasSomeCollapsible(); + this._register(this.viewModel.searchResult.onChange(() => updateHasSomeCollapsible())); + this._register(this.tree.onDidChangeCollapseState(() => updateHasSomeCollapsible())); this._register(Event.debounce(this.tree.onDidOpen, (last, event) => event, 75, true)(options => { if (options.element instanceof Match) { @@ -790,6 +780,19 @@ export class SearchView extends ViewPane { }); } + private hasSomeCollapsible(): boolean { + const viewer = this.getControl(); + const navigator = viewer.navigate(); + let node = navigator.first(); + do { + if (!viewer.isCollapsed(node)) { + return true; + } + } while (node = navigator.next()); + + return false; + } + selectNextMatch(): void { if (!this.hasSearchResults()) { return; @@ -1053,10 +1056,6 @@ export class SearchView extends ViewPane { return this.tree; } - isSlowSearch(): boolean { - return this.state === SearchUIState.SlowSearch; - } - allSearchFieldsClear(): boolean { return this.searchWidget.getReplaceValue() === '' && this.searchWidget.searchInput.getValue() === ''; @@ -1071,10 +1070,6 @@ export class SearchView extends ViewPane { return !this.viewModel.searchResult.isEmpty(); } - hasSearchPattern(): boolean { - return this.searchWidget && this.searchWidget.searchInput.getValue().length > 0; - } - clearSearchResults(clearInput = true): void { this.viewModel.searchResult.clear(); this.showEmptyStage(true); @@ -1088,7 +1083,6 @@ export class SearchView extends ViewPane { this.searchWidget.clear(); } this.viewModel.cancelSearch(); - this.updateActions(); this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search"); aria.status(nls.localize('ariaSearchResultsClearStatus', "The search results have been cleared")); @@ -1415,7 +1409,6 @@ export class SearchView extends ViewPane { const slowTimer = setTimeout(() => { this.state = SearchUIState.SlowSearch; - this.updateActions(); }, 2000); const onComplete = (completed?: ISearchComplete) => { @@ -1438,9 +1431,7 @@ export class SearchView extends ViewPane { this.viewModel.replaceString = this.searchWidget.getReplaceValue(); - this.updateActions(); const hasResults = !this.viewModel.searchResult.isEmpty(); - if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { return; } @@ -1521,7 +1512,6 @@ export class SearchView extends ViewPane { if (errors.isPromiseCanceledError(e)) { return onComplete(undefined); } else { - this.updateActions(); progressComplete(); this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR }); this.viewModel.searchResult.clear(); @@ -1532,8 +1522,6 @@ export class SearchView extends ViewPane { let visibleMatches = 0; - let updatedActionsForFileCount = false; - // Handle UI updates in an interval to show frequent progress and results const uiRefreshHandle: any = setInterval(() => { if (this.state === SearchUIState.Idle) { @@ -1547,11 +1535,6 @@ export class SearchView extends ViewPane { visibleMatches = fileCount; this.refreshAndUpdateCount(); } - - if (fileCount > 0 && !updatedActionsForFileCount) { - updatedActionsForFileCount = true; - this.updateActions(); - } }, 100); this.searchWidget.setReplaceAllActionState(false); @@ -1671,9 +1654,6 @@ export class SearchView extends ViewPane { } private showEmptyStage(forceHideMessages = false): void { - // disable 'result'-actions - this.updateActions(); - const showingCancelled = (this.messagesElement.firstChild?.textContent?.indexOf(SEARCH_CANCELLED_MESSAGE) ?? -1) > -1; // clean up ui @@ -1808,16 +1788,6 @@ export class SearchView extends ViewPane { } } - getActions(): IAction[] { - return [ - this.state === SearchUIState.SlowSearch ? - this.cancelAction : - this.refreshAction, - ...this.actions, - this.toggleCollapseAction - ]; - } - private get searchConfig(): ISearchConfigurationProperties { return this.configurationService.getValue('search'); } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 38e04a3de24..63c8976ea9d 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -11,7 +11,6 @@ export const FocusActiveEditorCommandId = 'search.action.focusActiveEditor'; export const FocusSearchFromResults = 'search.action.focusSearchFromResults'; export const OpenMatch = 'search.action.openResult'; export const OpenMatchToSide = 'search.action.openResultToSide'; -export const CancelActionId = 'search.action.cancel'; export const RemoveActionId = 'search.action.remove'; export const CopyPathCommandId = 'search.action.copyPath'; export const CopyMatchCommandId = 'search.action.copyMatch'; @@ -46,3 +45,7 @@ export const FileMatchOrFolderMatchWithResourceFocusKey = new RawContextKey('fileMatchFocus', false); export const FolderFocusKey = new RawContextKey('folderMatchFocus', false); export const MatchFocusKey = new RawContextKey('matchFocus', false); +export const ViewHasSearchPatternKey = new RawContextKey('viewHasSearchPattern', false); +export const ViewHasReplacePatternKey = new RawContextKey('viewHasReplacePattern', false); +export const ViewHasFilePatternKey = new RawContextKey('viewHasFilePattern', false); +export const ViewHasSomeCollapsibleKey = new RawContextKey('viewHasSomeCollapsibleResult', false); diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index c0e440eb323..1720c9fa277 100644 --- a/src/vs/workbench/contrib/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -16,6 +16,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IFileService } from 'vs/platform/files/common/files'; import { IRange } from 'vs/editor/common/core/range'; import { isNumber } from 'vs/base/common/types'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export interface IWorkspaceSymbol { name: string; @@ -164,3 +165,11 @@ export function extractRangeFromFilter(filter: string, unless?: string[]): IFilt return undefined; } + +export enum SearchUIState { + Idle, + Searching, + SlowSearch +} + +export const SearchStateKey = new RawContextKey('searchState', SearchUIState.Idle); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 44b8afd2708..047af4bc3cc 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -12,7 +12,7 @@ import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKe 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 { ContextKeyEqualsExpr, 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 { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -24,14 +24,15 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo 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 { searchNewEditorIcon, 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 { createEditorFromSearchResult, modifySearchEditorContextLinesCommand, openNewSearchEditor, openSearchEditor, 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'; +import { VIEW_ID } from 'vs/workbench/services/search/common/search'; const OpenInEditorCommandId = 'search.action.openInEditor'; @@ -506,4 +507,25 @@ registerAction2(class extends Action2 { selectAllSearchEditorMatchesCommand(accessor); } }); + +registerAction2(class OpenSearchEditorAction extends Action2 { + constructor() { + super({ + id: SearchEditorConstants.OpenNewEditorCommandId, + title: localize('search.openNewEditor', "Open New Search Editor"), + category, + icon: searchNewEditorIcon, + f1: true, + menu: [{ + id: MenuId.ViewTitle, + group: 'navigation', + order: 2, + when: ContextKeyEqualsExpr.create('view', VIEW_ID), + }] + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { + return openSearchEditor(accessor); + } +}); //#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index ff5292c46c3..1cb5552de6c 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -3,34 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from 'vs/base/common/actions'; +import { Schemas } from 'vs/base/common/network'; +import { assertIsDefined, withNullAsUndefined } 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 { 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { EditorsOrder } from 'vs/workbench/common/editor'; +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 { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; +import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { searchNewEditorIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; -import { OpenNewEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; -import { EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IViewsService } from 'vs/workbench/common/views'; -import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; +import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); @@ -80,41 +75,23 @@ export const selectAllSearchEditorMatchesCommand = (accessor: ServicesAccessor) } }; -// Handler for the action bar entry in the search view. -export class OpenSearchEditorAction extends Action { - static readonly ID: string = OpenNewEditorCommandId; - static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor"); - - constructor(id: string, label: string, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IViewsService private readonly viewsService: IViewsService, - ) { - super(id, label, ThemeIcon.asClassName(searchNewEditorIcon)); - } - - update() { - // pass - } - - get enabled(): boolean { - return true; - } - - async run() { - const searchView = getSearchView(this.viewsService); - if (searchView) { - await this.instantiationService.invokeFunction(openNewSearchEditor, { - filesToInclude: searchView.searchIncludePattern.getValue(), - filesToExclude: searchView.searchExcludePattern.getValue(), - isRegexp: searchView.searchAndReplaceWidget.searchInput.getRegex(), - isCaseSensitive: searchView.searchAndReplaceWidget.searchInput.getCaseSensitive(), - matchWholeWord: searchView.searchAndReplaceWidget.searchInput.getWholeWords(), - useExcludeSettingsAndIgnoreFiles: searchView.searchExcludePattern.useExcludesAndIgnoreFiles(), - showIncludesExcludes: !!(searchView.searchIncludePattern.getValue() || searchView.searchExcludePattern.getValue() || !searchView.searchExcludePattern.useExcludesAndIgnoreFiles()) - }); - } else { - await this.instantiationService.invokeFunction(openNewSearchEditor); - } +export async function openSearchEditor(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const searchView = getSearchView(viewsService); + if (searchView) { + const instantiationService = searchView.getInstantiationService(); + await instantiationService.invokeFunction(openNewSearchEditor, { + filesToInclude: searchView.searchIncludePattern.getValue(), + filesToExclude: searchView.searchExcludePattern.getValue(), + isRegexp: searchView.searchAndReplaceWidget.searchInput.getRegex(), + isCaseSensitive: searchView.searchAndReplaceWidget.searchInput.getCaseSensitive(), + matchWholeWord: searchView.searchAndReplaceWidget.searchInput.getWholeWords(), + useExcludeSettingsAndIgnoreFiles: searchView.searchExcludePattern.useExcludesAndIgnoreFiles(), + showIncludesExcludes: !!(searchView.searchIncludePattern.getValue() || searchView.searchExcludePattern.getValue() || !searchView.searchExcludePattern.useExcludesAndIgnoreFiles()) + }); + } else { + const instantiationService = accessor.get(IInstantiationService); + await instantiationService.invokeFunction(openNewSearchEditor); } } -- GitLab