未验证 提交 e3509b62 编写于 作者: J Jackson Kearl 提交者: GitHub

Add search.mode option to control default search experience (#114015)

* Add search.mode option to control default search experience

* Move resource serializer to Query Builder
上级 d110d503
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import { distinct } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
......@@ -13,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel';
import * as nls from 'vs/nls';
import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
import { CommandsRegistry, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService, ConfigurationTarget } 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';
......@@ -40,7 +39,7 @@ 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';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder } from 'vs/workbench/services/search/common/search';
import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder, ISearchConfiguration } 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';
......@@ -55,6 +54,7 @@ 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);
......@@ -404,29 +404,40 @@ const FocusSearchListCommand: ICommandAction = {
};
MenuRegistry.addCommand(FocusSearchListCommand);
const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => {
const searchInFolderCommand: ICommandHandler = async (accessor, resource?: URI) => {
const listService = accessor.get(IListService);
const fileService = accessor.get(IFileService);
const viewsService = accessor.get(IViewsService);
const contextService = accessor.get(IWorkspaceContextService);
const commandService = accessor.get(ICommandService);
const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService));
const searchConfig = accessor.get(IConfigurationService).getValue<ISearchConfiguration>().search;
const mode = searchConfig.mode;
const resolvedResources = fileService.resolveAll(resources.map(resource => ({ resource }))).then(results => {
const folders: URI[] = [];
results.forEach(result => {
if (result.success && result.stat) {
folders.push(result.stat.isDirectory ? result.stat.resource : dirname(result.stat.resource));
}
});
return resolveResourcesForSearchIncludes(folders, contextService);
});
return openSearchView(viewsService, true).then(searchView => {
if (mode === 'view') {
const searchView = await openSearchView(viewsService, true);
if (resources && resources.length && searchView) {
return fileService.resolveAll(resources.map(resource => ({ resource }))).then(results => {
const folders: URI[] = [];
results.forEach(result => {
if (result.success && result.stat) {
folders.push(result.stat.isDirectory ? result.stat.resource : dirname(result.stat.resource));
}
});
searchView.searchInFolders(distinct(folders, folder => folder.toString()));
});
searchView.searchInFolders(await resolvedResources);
}
return undefined;
});
} else {
return commandService.executeCommand(SearchEditorConstants.OpenEditorCommandId, {
filesToInclude: (await resolvedResources).join(', '),
showIncludesExcludes: true,
location: mode === 'newEditor' ? 'new' : 'reuse',
});
}
};
const FIND_IN_FOLDER_ID = 'filesExplorer.findInFolder';
......@@ -455,12 +466,22 @@ CommandsRegistry.registerCommand({
const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace';
CommandsRegistry.registerCommand({
id: FIND_IN_WORKSPACE_ID,
handler: (accessor) => {
return openSearchView(accessor.get(IViewsService), true).then(searchView => {
handler: async (accessor) => {
const searchConfig = accessor.get(IConfigurationService).getValue<ISearchConfiguration>().search;
const mode = searchConfig.mode;
if (mode === 'view') {
const searchView = await openSearchView(accessor.get(IViewsService), true);
if (searchView) {
searchView.searchInFolders();
}
});
}
else {
return accessor.get(ICommandService).executeCommand(SearchEditorConstants.OpenEditorCommandId, {
location: mode === 'newEditor' ? 'new' : 'reuse',
filesToInclude: '',
});
}
}
});
......@@ -724,6 +745,17 @@ configurationRegistry.registerConfiguration({
},
scope: ConfigurationScope.RESOURCE
},
'search.mode': {
type: 'string',
enum: ['view', 'reuseEditor', 'newEditor'],
default: 'view',
markdownDescription: nls.localize('search.mode', "Controls where new `Search: Find in Files` and `Find in Folder` operations occur: either in the sidebar's search view, or in a search editor"),
enumDescriptions: [
nls.localize('search.mode.view', "Search in the search view, either in the panel or sidebar."),
nls.localize('search.mode.reuseEditor', "Search in an existing search editor if present, otherwise in a new search editor"),
nls.localize('search.mode.newEditor', "Search in a new search editor"),
]
},
'search.useRipgrep': {
type: 'boolean',
description: nls.localize('useRipgrep', "This setting is deprecated and now falls back on \"search.usePCRE2\"."),
......
......@@ -10,7 +10,7 @@ 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 } from 'vs/platform/commands/common/commands';
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';
......@@ -30,6 +30,8 @@ import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEd
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';
export function isSearchViewFocused(viewsService: IViewsService): boolean {
const searchView = getSearchView(viewsService);
......@@ -173,21 +175,37 @@ export interface IFindInFilesArgs {
useExcludeSettingsAndIgnoreFiles?: boolean;
}
export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFilesArgs = {}) => {
const viewsService = accessor.get(IViewsService);
openSearchView(viewsService, false).then(openedView => {
if (openedView) {
const searchAndReplaceWidget = openedView.searchAndReplaceWidget;
searchAndReplaceWidget.toggleReplace(typeof args.replace === 'string');
let updatedText = false;
if (typeof args.query === 'string') {
openedView.setSearchParameters(args);
} else {
updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' });
const searchConfig = accessor.get(IConfigurationService).getValue<ISearchConfiguration>().search;
const mode = searchConfig.mode;
if (mode === 'view') {
const viewsService = accessor.get(IViewsService);
openSearchView(viewsService, false).then(openedView => {
if (openedView) {
const searchAndReplaceWidget = openedView.searchAndReplaceWidget;
searchAndReplaceWidget.toggleReplace(typeof args.replace === 'string');
let updatedText = false;
if (typeof args.query === 'string') {
openedView.setSearchParameters(args);
} else {
updatedText = openedView.updateTextFromFindWidgetOrSelection({ allowUnselectedWord: typeof args.replace !== 'string' });
}
openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText);
}
openedView.searchAndReplaceWidget.focus(undefined, updatedText, updatedText);
}
});
});
} else {
const convertArgs = (args: IFindInFilesArgs): OpenSearchEditorArgs => ({
location: mode === 'newEditor' ? 'new' : 'reuse',
query: args.query,
filesToInclude: args.filesToInclude,
filesToExclude: args.filesToExclude,
matchWholeWord: args.matchWholeWord,
isCaseSensitive: args.isCaseSensitive,
isRegexp: args.isRegex,
useExcludeSettingsAndIgnoreFiles: args.useExcludeSettingsAndIgnoreFiles,
showIncludesExcludes: !!(args.filesToExclude || args.filesToExclude || !args.useExcludeSettingsAndIgnoreFiles),
});
accessor.get(ICommandService).executeCommand(OpenEditorCommandId, convertArgs(args));
}
};
export class OpenSearchViewletAction extends FindOrReplaceInFilesAction {
......
......@@ -55,7 +55,6 @@ import { FileMatch, FileMatchOrMatch, IChangeEvent, ISearchWorkbenchService, Mat
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { relativePath } from 'vs/base/common/resources';
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';
......@@ -1269,45 +1268,7 @@ export class SearchView extends ViewPane {
}
}
searchInFolders(resources?: URI[]): void {
const folderPaths: string[] = [];
const workspace = this.contextService.getWorkspace();
if (resources) {
resources.forEach(resource => {
let folderPath: string | undefined;
if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
// Show relative path from the root for single-root mode
folderPath = relativePath(workspace.folders[0].uri, resource); // always uses forward slashes
if (folderPath && folderPath !== '.') {
folderPath = './' + folderPath;
}
} else {
const owningFolder = this.contextService.getWorkspaceFolder(resource);
if (owningFolder) {
const owningRootName = owningFolder.name;
// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1;
if (isUniqueFolder) {
const relPath = relativePath(owningFolder.uri, resource); // always uses forward slashes
if (relPath === '') {
folderPath = `./${owningFolder.name}`;
} else {
folderPath = `./${owningFolder.name}/${relPath}`;
}
} else {
folderPath = resource.fsPath; // TODO rob: handle on-file URIs
}
}
}
if (folderPath) {
folderPaths.push(folderPath);
}
});
}
searchInFolders(folderPaths: string[] = []): void {
if (!folderPaths.length || folderPaths.some(folderPath => folderPath === '.')) {
this.inputPatternIncludes.setValue('');
this.searchWidget.focus();
......
......@@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob';
import { untildify } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import * as path from 'vs/base/common/path';
import { isEqual, basename } from 'vs/base/common/resources';
import { isEqual, basename, relativePath } from 'vs/base/common/resources';
import * as strings from 'vs/base/common/strings';
import { URI, URI as uri } from 'vs/base/common/uri';
import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch';
......@@ -517,3 +517,48 @@ function normalizeGlobPattern(pattern: string): string {
.replace(/^\.\//, '')
.replace(/\/+$/g, '');
}
/**
* Construct an include pattern from a list of folders uris to search in.
*/
export function resolveResourcesForSearchIncludes(resources: URI[], contextService: IWorkspaceContextService): string[] {
resources = arrays.distinct(resources, resource => resource.toString());
const folderPaths: string[] = [];
const workspace = contextService.getWorkspace();
if (resources) {
resources.forEach(resource => {
let folderPath: string | undefined;
if (contextService.getWorkbenchState() === WorkbenchState.FOLDER) {
// Show relative path from the root for single-root mode
folderPath = relativePath(workspace.folders[0].uri, resource); // always uses forward slashes
if (folderPath && folderPath !== '.') {
folderPath = './' + folderPath;
}
} else {
const owningFolder = contextService.getWorkspaceFolder(resource);
if (owningFolder) {
const owningRootName = owningFolder.name;
// If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path
const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1;
if (isUniqueFolder) {
const relPath = relativePath(owningFolder.uri, resource); // always uses forward slashes
if (relPath === '') {
folderPath = `./${owningFolder.name}`;
} else {
folderPath = `./${owningFolder.name}/${relPath}`;
}
} else {
folderPath = resource.fsPath; // TODO rob: handle non-file URIs
}
}
}
if (folderPath) {
folderPaths.push(folderPath);
}
});
}
return folderPaths;
}
......@@ -261,7 +261,7 @@ registerAction2(class extends Action2 {
});
}
async run(accessor: ServicesAccessor, args: LegacySearchEditorArgs | OpenSearchEditorArgs) {
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, translateLegacyConfig({ ...args, location: 'new' }));
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, translateLegacyConfig({ location: 'new', ...args }));
}
});
......@@ -276,7 +276,7 @@ registerAction2(class extends Action2 {
});
}
async run(accessor: ServicesAccessor, args: LegacySearchEditorArgs | OpenSearchEditorArgs) {
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, translateLegacyConfig({ ...args, location: 'reuse' }));
await accessor.get(IInstantiationService).invokeFunction(openNewSearchEditor, translateLegacyConfig({ location: 'reuse', ...args }));
}
});
......
......@@ -567,6 +567,18 @@ export class SearchEditor extends BaseTextEditor {
return this._input as SearchEditorInput;
}
setSearchConfig(config: Partial<Readonly<SearchConfiguration>>) {
if (config.query !== undefined) { this.queryEditorWidget.setValue(config.query); }
if (config.isCaseSensitive !== undefined) { this.queryEditorWidget.searchInput.setCaseSensitive(config.isCaseSensitive); }
if (config.isRegexp !== undefined) { this.queryEditorWidget.searchInput.setRegex(config.isRegexp); }
if (config.matchWholeWord !== undefined) { this.queryEditorWidget.searchInput.setWholeWords(config.matchWholeWord); }
if (config.contextLines !== undefined) { this.queryEditorWidget.setContextLines(config.contextLines); }
if (config.filesToExclude !== undefined) { this.inputPatternExcludes.setValue(config.filesToExclude); }
if (config.filesToInclude !== undefined) { this.inputPatternIncludes.setValue(config.filesToInclude); }
if (config.useExcludeSettingsAndIgnoreFiles !== undefined) { this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(config.useExcludeSettingsAndIgnoreFiles); }
if (config.showIncludesExcludes !== undefined) { this.toggleIncludesExcludes(config.showIncludesExcludes); }
}
async setInput(newInput: SearchEditorInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
this.saveViewState();
......@@ -581,15 +593,7 @@ export class SearchEditor extends BaseTextEditor {
this.toggleRunAgainMessage(body.getLineCount() === 1 && body.getValue() === '' && config.query !== '');
this.queryEditorWidget.setValue(config.query);
this.queryEditorWidget.searchInput.setCaseSensitive(config.isCaseSensitive);
this.queryEditorWidget.searchInput.setRegex(config.isRegexp);
this.queryEditorWidget.searchInput.setWholeWords(config.matchWholeWord);
this.queryEditorWidget.setContextLines(config.contextLines);
this.inputPatternExcludes.setValue(config.filesToExclude);
this.inputPatternIncludes.setValue(config.filesToInclude);
this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(config.useExcludeSettingsAndIgnoreFiles);
this.toggleIncludesExcludes(config.showIncludesExcludes);
this.setSearchConfig(config);
this.restoreViewState();
......
......@@ -80,8 +80,8 @@ 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");
......@@ -156,7 +156,7 @@ export const openNewSearchEditor =
telemetryService.publicLog2('searchEditor/openNewSearchEditor');
const args: OpenSearchEditorArgs = { query: selected };
const args: OpenSearchEditorArgs = { query: selected || undefined };
Object.entries(_args).forEach(([name, value]) => {
(args as any)[name as any] = (typeof value === 'string') ? configurationResolverService.resolve(lastActiveWorkspaceRoot, value) : value;
});
......@@ -167,6 +167,7 @@ export const openNewSearchEditor =
editor = assertIsDefined(await assertIsDefined(editorGroupsService.getGroup(existing.groupId)).openEditor(input)) as SearchEditor;
if (selected) { editor.setQuery(selected); }
else { editor.selectQuery(); }
editor.setSearchConfig(args);
} else {
const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { config: args, text: '' });
editor = await editorService.openEditor(input, { pinned: true }, toSide ? SIDE_GROUP : ACTIVE_GROUP) as SearchEditor;
......
......@@ -365,6 +365,7 @@ export interface ISearchConfigurationProperties {
seedOnFocus: boolean;
seedWithNearestWord: boolean;
searchOnTypeDebouncePeriod: number;
mode: 'view' | 'reuseEditor' | 'newEditor';
searchEditor: {
doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide',
reusePriorSearchConfiguration: boolean,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册