From f56d7fcd24c800fcf64b532a1427c1d44ccfdafe Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Tue, 29 Sep 2020 16:00:44 -0700 Subject: [PATCH 01/11] Initial work on "search in open editors" --- .../search/browser/patternInputWidget.ts | 67 +++++++++++++++++-- .../search/browser/search.contribution.ts | 5 ++ .../contrib/search/browser/searchView.ts | 11 ++- .../contrib/search/common/queryBuilder.ts | 20 ++++++ .../searchEditor/browser/searchEditor.ts | 11 +-- .../searchEditor/browser/searchEditorInput.ts | 1 + .../browser/searchEditorSerialization.ts | 4 ++ .../services/search/common/search.ts | 5 ++ 8 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 63bf2771a464d..fea2c182bb431 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -18,7 +18,8 @@ import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedH import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import type { IThemable } from 'vs/base/common/styler'; import { Codicon } from 'vs/base/common/codicons'; - +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ISearchConfiguration } from 'vs/workbench/services/search/common/search'; export interface IOptions { placeholder?: string; width?: number; @@ -50,7 +51,8 @@ export class PatternInputWidget extends Widget implements IThemable { constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService protected themeService: IThemeService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IConfigurationService protected readonly configurationService: IConfigurationService ) { super(); this.width = options.width || 100; @@ -178,6 +180,62 @@ export class PatternInputWidget extends Widget implements IThemable { } } +export class IncludePatternInputWidget extends PatternInputWidget { + + private _onChangeSearchInEditorsBoxEmitter = this._register(new Emitter()); + onChangeSearchInEditorsBox = this._onChangeSearchInEditorsBoxEmitter.event; + + constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), + @IThemeService themeService: IThemeService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + ) { + super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService); + } + + private useSearchInEditorsBox!: Checkbox; + + dispose(): void { + super.dispose(); + this.useSearchInEditorsBox.dispose(); + } + + onlySearchInOpenEditors(): boolean { + return this.useSearchInEditorsBox.checked; + } + + setOnlySearchInOpenEditors(value: boolean) { + this.useSearchInEditorsBox.checked = value; + } + + protected getSubcontrolsWidth(): number { + if (this.configurationService.getValue().search?.experimental?.searchInOpenEditors) { + return super.getSubcontrolsWidth() + this.useSearchInEditorsBox.width(); + } + return super.getSubcontrolsWidth(); + } + + protected renderSubcontrols(controlsDiv: HTMLDivElement): void { + this.useSearchInEditorsBox = this._register(new Checkbox({ + icon: Codicon.book, + title: nls.localize('onlySearchInOpenEditors', "Search Only in Open Editors"), + isChecked: false, + })); + if (!this.configurationService.getValue().search?.experimental?.searchInOpenEditors) { + return; + } + this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => { + this._onChangeSearchInEditorsBoxEmitter.fire(); + if (!viaKeyboard) { + this.inputBox.focus(); + } + })); + this._register(attachCheckboxStyler(this.useSearchInEditorsBox, this.themeService)); + controlsDiv.appendChild(this.useSearchInEditorsBox.domNode); + super.renderSubcontrols(controlsDiv); + } +} + export class ExcludePatternInputWidget extends PatternInputWidget { private _onChangeIgnoreBoxEmitter = this._register(new Emitter()); @@ -185,9 +243,10 @@ export class ExcludePatternInputWidget extends PatternInputWidget { constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null), @IThemeService themeService: IThemeService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(parent, contextViewProvider, options, themeService, contextKeyService); + super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService); } private useExcludesAndIgnoreFilesBox!: Checkbox; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index ebda880f06653..c1f04f5e8bddd 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -998,6 +998,11 @@ configurationRegistry.registerConfiguration({ ], 'description': nls.localize('search.sortOrder', "Controls sorting order of search results.") }, + 'search.experimental.searchInOpenEditors': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('search.experimental.searchInOpenEditors', "Experimental. When enabled, an option is provided to make workspace search only search files that have been opened. **Requires restart to take effect.**") + } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 232a61580e707..1a3a3705f747d 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -54,7 +54,7 @@ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/vie 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 { ExcludePatternInputWidget, IncludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; 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'; @@ -125,7 +125,7 @@ export class SearchView extends ViewPane { private queryDetails!: HTMLElement; private toggleQueryDetailsButton!: HTMLElement; private inputPatternExcludes!: ExcludePatternInputWidget; - private inputPatternIncludes!: PatternInputWidget; + private inputPatternIncludes!: IncludePatternInputWidget; private resultsElement!: HTMLElement; private currentSelectedFileMatch: FileMatch | undefined; @@ -309,14 +309,17 @@ export class SearchView extends ViewPane { const filesToIncludeTitle = nls.localize('searchScope.includes', "files to include"); dom.append(folderIncludesList, $('h4', undefined, filesToIncludeTitle)); - this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { + this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: nls.localize('label.includes', 'Search Include Patterns'), history: patternIncludesHistory, })); this.inputPatternIncludes.setValue(patternIncludes); + this._register(this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod }))); this._register(this.inputPatternIncludes.onCancel(() => this.cancelSearch(false))); + this._register(this.inputPatternIncludes.onChangeSearchInEditorsBox(() => this.triggerQueryChange())); + this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused); // excludes list @@ -1293,6 +1296,7 @@ export class SearchView extends ViewPane { const excludePatternText = this.inputPatternExcludes.getValue().trim(); const includePatternText = this.inputPatternIncludes.getValue().trim(); const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); + const onlySearchInOpenEditors = this.inputPatternIncludes.onlySearchInOpenEditors(); if (contentPattern.length === 0) { this.clearSearchResults(false); @@ -1321,6 +1325,7 @@ export class SearchView extends ViewPane { maxResults: SearchView.MAX_TEXT_RESULTS, disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined, disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined, + onlyOpenEditors: onlySearchInOpenEditors, excludePattern, includePattern, previewOptions: { diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index d8a1a911e4568..ffe88898502bf 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -16,6 +16,7 @@ import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService, IWorkspaceFolderData, toWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; @@ -59,6 +60,7 @@ export interface ICommonQueryBuilderOptions { disregardExcludeSettings?: boolean; disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; + onlyOpenEditors?: boolean; } export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -81,6 +83,7 @@ export class QueryBuilder { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, @IPathService private readonly pathService: IPathService ) { } @@ -178,9 +181,26 @@ export class QueryBuilder { excludePattern: excludeSearchPathsInfo.pattern, includePattern: includeSearchPathsInfo.pattern, + onlyOpenEditors: options.onlyOpenEditors, maxResults: options.maxResults }; + if (options.onlyOpenEditors) { + const openEditors = arrays.coalesce(arrays.flatten(this.editorGroupsService.groups.map(group => group.editors.map(editor => editor.resource)))); + const openEditorsInQuery = openEditors.filter(editor => pathIncludedInQuery(queryProps, editor.fsPath)); + const openEditorIncludes = openEditorsInQuery.map(editor => { + const workspace = this.workspaceContextService.getWorkspaceFolder(editor); + if (workspace) { + const relPath = path.relative(workspace?.uri.fsPath, editor.fsPath); + return includeFolderName ? `./${workspace.name}/${relPath}` : `./${relPath}`; + } + else { + return editor.fsPath.replace(/^\//, ''); + } + }); + // return this.commonQuery(folderResources, { ...options, expandPatterns: false, onlyOpenEditors: false, includePattern: openEditorIncludes.join(', ') }); + } + // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 9a4f7b0e09e78..0927118a7c021 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -38,7 +38,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; -import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; +import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; @@ -67,7 +67,7 @@ export class SearchEditor extends BaseTextEditor { private searchResultEditor!: CodeEditorWidget; private queryEditorContainer!: HTMLElement; private dimension?: DOM.Dimension; - private inputPatternIncludes!: PatternInputWidget; + private inputPatternIncludes!: IncludePatternInputWidget; private inputPatternExcludes!: ExcludePatternInputWidget; private includesExcludesContainer!: HTMLElement; private toggleQueryDetailsButton!: HTMLElement; @@ -168,10 +168,11 @@ export class SearchEditor extends BaseTextEditor { const folderIncludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.includes')); const filesToIncludeTitle = localize('searchScope.includes', "files to include"); DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle)); - this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { + this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: localize('label.includes', 'Search Include Patterns'), })); this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); + this._register(this.inputPatternIncludes.onChangeSearchInEditorsBox(() => this.triggerSearch())); // // Excludes const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes')); @@ -181,7 +182,7 @@ export class SearchEditor extends BaseTextEditor { ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), })); this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 })); - this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch()); + this._register(this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch())); [this.queryEditorWidget.searchInput, this.inputPatternIncludes, this.inputPatternExcludes].map(input => this._register(attachInputBoxStyler(input, this.themeService, { inputBorder: searchEditorTextInputBorder }))); @@ -449,6 +450,7 @@ export class SearchEditor extends BaseTextEditor { isRegexp: this.queryEditorWidget.searchInput.getRegex(), matchWholeWord: this.queryEditorWidget.searchInput.getWholeWords(), useExcludeSettingsAndIgnoreFiles: this.inputPatternExcludes.useExcludesAndIgnoreFiles(), + onlyOpenEditors: this.inputPatternIncludes.onlySearchInOpenEditors(), showIncludesExcludes: this.showingIncludesExcludes }; } @@ -483,6 +485,7 @@ export class SearchEditor extends BaseTextEditor { disregardExcludeSettings: !config.useExcludeSettingsAndIgnoreFiles || undefined, excludePattern: config.filesToExclude, includePattern: config.filesToInclude, + onlyOpenEditors: config.onlyOpenEditors, previewOptions: { matchLines: 1, charsPerLine: 1000 diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index afb8c5aa4878f..9d02af8f6d715 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -40,6 +40,7 @@ export type SearchConfiguration = { isRegexp: boolean, useExcludeSettingsAndIgnoreFiles: boolean, showIncludesExcludes: boolean, + onlyOpenEditors: boolean, }; export const SEARCH_EDITOR_EXT = '.code-search'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index 951be05422f0a..51ccafc40aacd 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -115,6 +115,7 @@ const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: stri showIncludesExcludes: !!(includes || excludes || pattern?.userDisabledExcludesAndIgnoreFiles), useExcludeSettingsAndIgnoreFiles: (pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? true : !pattern.userDisabledExcludesAndIgnoreFiles), contextLines, + onlyOpenEditors: !!pattern.onlyOpenEditors, }; }; @@ -131,6 +132,7 @@ export const serializeSearchConfiguration = (config: Partial ({ matchWholeWord: false, contextLines: 0, showIncludesExcludes: false, + onlyOpenEditors: false, }); export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguration => { @@ -197,6 +200,7 @@ export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguratio query.isCaseSensitive = value.indexOf('CaseSensitive') !== -1; query.useExcludeSettingsAndIgnoreFiles = value.indexOf('IgnoreExcludeSettings') === -1; query.matchWholeWord = value.indexOf('WordMatch') !== -1; + query.onlyOpenEditors = value.indexOf('OpenEditors') !== -1; } } } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index f6af1abf865c1..4ed641bb79fef 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -77,6 +77,8 @@ export interface ICommonQueryProps { excludePattern?: glob.IExpression; extraFileResources?: U[]; + onlyOpenEditors?: boolean; + maxResults?: number; usingSearchPaths?: boolean; } @@ -372,6 +374,9 @@ export interface ISearchConfigurationProperties { defaultNumberOfContextLines: number | null, experimental: {} }; + experimental: { + searchInOpenEditors: boolean + } sortOrder: SearchSortOrder; } From cc9023ae535330933e36c32234068836ed2c54c4 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 20 Jan 2021 11:49:31 -0800 Subject: [PATCH 02/11] Update wording --- .../search/browser/patternInputWidget.ts | 2 +- .../contrib/search/common/queryBuilder.ts | 24 +++++++------------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index fea2c182bb431..d59e779686d19 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -218,7 +218,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { protected renderSubcontrols(controlsDiv: HTMLDivElement): void { this.useSearchInEditorsBox = this._register(new Checkbox({ icon: Codicon.book, - title: nls.localize('onlySearchInOpenEditors', "Search Only in Open Editors"), + title: nls.localize('onlySearchInOpenEditors', "Search only in Open Editors"), isChecked: false, })); if (!this.configurationService.getValue().search?.experimental?.searchInOpenEditors) { diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index ffe88898502bf..aeee8a3170700 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -175,7 +175,7 @@ export class QueryBuilder { const queryProps: ICommonQueryProps = { _reason: options._reason, - folderQueries, + folderQueries: options.onlyOpenEditors ? [] : folderQueries, usingSearchPaths: !!(includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length), extraFileResources: options.extraFileResources, @@ -188,22 +188,14 @@ export class QueryBuilder { if (options.onlyOpenEditors) { const openEditors = arrays.coalesce(arrays.flatten(this.editorGroupsService.groups.map(group => group.editors.map(editor => editor.resource)))); const openEditorsInQuery = openEditors.filter(editor => pathIncludedInQuery(queryProps, editor.fsPath)); - const openEditorIncludes = openEditorsInQuery.map(editor => { - const workspace = this.workspaceContextService.getWorkspaceFolder(editor); - if (workspace) { - const relPath = path.relative(workspace?.uri.fsPath, editor.fsPath); - return includeFolderName ? `./${workspace.name}/${relPath}` : `./${relPath}`; - } - else { - return editor.fsPath.replace(/^\//, ''); - } - }); - // return this.commonQuery(folderResources, { ...options, expandPatterns: false, onlyOpenEditors: false, includePattern: openEditorIncludes.join(', ') }); + queryProps.extraFileResources = openEditorsInQuery; + queryProps.folderQueries = []; + } + else { + // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace + const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); + queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; } - - // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace - const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); - queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; return queryProps; } From ffa6e7e87447bb2cb84cf3c6676198888b9708cb Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 20 Jan 2021 11:55:40 -0800 Subject: [PATCH 03/11] Update messaging for open editors config --- .../contrib/search/browser/searchView.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 1a3a3705f747d..560f04115d50c 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1448,14 +1448,26 @@ export class SearchView extends ViewPane { if (!completed) { message = SEARCH_CANCELLED_MESSAGE; - } else if (hasIncludes && hasExcludes) { - message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); - } else if (hasIncludes) { - message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText); - } else if (hasExcludes) { - message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText); + } else if (this.inputPatternIncludes.onlySearchInOpenEditors()) { + if (hasIncludes && hasExcludes) { + message = nls.localize('noOpenEditorResultsIncludesExcludes', "No results found in open editors matching '{0}' excluding '{1}' - ", includePatternText, excludePatternText); + } else if (hasIncludes) { + message = nls.localize('noOpenEditorResultsIncludes', "No results found in open editors matching '{0}' - ", includePatternText); + } else if (hasExcludes) { + message = nls.localize('noOpenEditorResultsExcludes', "No results found in open editors excluding '{0}' - ", excludePatternText); + } else { + message = nls.localize('noOpenEditorResultsFound', "No results found in open editors. Review your settings for configured exclusions and check your gitignore files - "); + } } else { - message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and check your gitignore files - "); + if (hasIncludes && hasExcludes) { + message = nls.localize('noResultsIncludesExcludes', "No results found in '{0}' excluding '{1}' - ", includePatternText, excludePatternText); + } else if (hasIncludes) { + message = nls.localize('noResultsIncludes', "No results found in '{0}' - ", includePatternText); + } else if (hasExcludes) { + message = nls.localize('noResultsExcludes', "No results found excluding '{0}' - ", excludePatternText); + } else { + message = nls.localize('noResultsFound', "No results found. Review your settings for configured exclusions and check your gitignore files - "); + } } // Indicate as status to ARIA From 8856a0086c4c3b1e51c1dd578936ad296581c1f1 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Wed, 20 Jan 2021 15:58:47 -0800 Subject: [PATCH 04/11] Add command to open all git changes (in association with searching in all open editors) --- extensions/git/package.json | 5 +++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/extensions/git/package.json b/extensions/git/package.json index 80526a43d271a..0bbf3ae575773 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -71,6 +71,11 @@ "category": "Git", "icon": "$(compare-changes)" }, + { + "command": "git.openAllChanges", + "title": "%command.openAllChanges%", + "category": "Git" + }, { "command": "git.openFile", "title": "%command.openFile%", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 8d18564961ea1..f76c42dbb550e 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -9,6 +9,7 @@ "command.close": "Close Repository", "command.refresh": "Refresh", "command.openChange": "Open Changes", + "command.openAllChanges": "Open All Changes", "command.openFile": "Open File", "command.openHEADFile": "Open File (HEAD)", "command.stage": "Stage Changes", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index bdd13a197bf0f..ac9249871fcaa 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -372,6 +372,20 @@ export class CommandCenter { await resource.open(); } + @command('git.openAllChanges', { repository: true }) + async openChanges(repository: Repository): Promise { + [ + ...repository.workingTreeGroup.resourceStates, + ...repository.untrackedGroup.resourceStates, + ].forEach(resource => { + commands.executeCommand( + 'vscode.open', + resource.resourceUri, + { preview: false, } + ); + }); + } + async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise { if (!url || typeof url !== 'string') { url = await pickRemoteSource(this.model, { From f3a07cf6dc0cafc1c372982e725cec4122c6e588 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 10:55:17 -0800 Subject: [PATCH 05/11] Add strict parsing mode to search using providers for specific files --- .../contrib/search/browser/searchView.ts | 1 + .../contrib/search/common/queryBuilder.ts | 64 ++++++++++++------- .../services/search/common/search.ts | 32 ++++++---- .../search/node/ripgrepTextSearchEngine.ts | 15 ++--- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 560f04115d50c..89fd017aa68db 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1489,6 +1489,7 @@ export class SearchView extends ViewPane { this.inputPatternExcludes.setValue(''); this.inputPatternIncludes.setValue(''); + this.inputPatternIncludes.setOnlySearchInOpenEditors(false); this.triggerQueryChange({ preserveFocus: false }); })); diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index aeee8a3170700..ce9321c5156a7 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -151,20 +151,21 @@ export class QueryBuilder { }; } - private handleIncludeExclude(pattern: string | string[] | undefined, expandPatterns: boolean | undefined): ISearchPathsInfo { + private handleIncludeExclude(pattern: string | string[] | undefined, expandPatterns: 'strict' | 'loose' | 'none'): ISearchPathsInfo { if (!pattern) { return {}; } pattern = Array.isArray(pattern) ? pattern.map(normalizeSlashes) : normalizeSlashes(pattern); - return expandPatterns ? - this.parseSearchPaths(pattern) : - { pattern: patternListToIExpression(...(Array.isArray(pattern) ? pattern : [pattern])) }; + return expandPatterns === 'none' ? + { pattern: patternListToIExpression(...(Array.isArray(pattern) ? pattern : [pattern])) } : + this.parseSearchPaths(pattern, expandPatterns === 'strict'); } - private commonQuery(folderResources: (IWorkspaceFolderData | URI)[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { - const includeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.includePattern, options.expandPatterns); - const excludeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.excludePattern, options.expandPatterns); + private commonQuery(folderResources: (IWorkspaceFolderData | URI)[] = [], options: ICommonQueryBuilderOptions = {}, strict?: boolean): ICommonQueryProps { + const patternExpansionMode = strict ? 'strict' : options.expandPatterns ? 'loose' : 'none'; + const includeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.includePattern, patternExpansionMode); + const excludeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.excludePattern, patternExpansionMode); // Build folderQueries from searchPaths, if given, otherwise folderResources const includeFolderName = folderResources.length > 1; @@ -185,18 +186,35 @@ export class QueryBuilder { maxResults: options.maxResults }; + // When "onlyOpenEditors" is enabled, filter all opened editors by the existing include/exclude patterns, + // then rerun the query build setting the includes to those remaining editors if (options.onlyOpenEditors) { const openEditors = arrays.coalesce(arrays.flatten(this.editorGroupsService.groups.map(group => group.editors.map(editor => editor.resource)))); const openEditorsInQuery = openEditors.filter(editor => pathIncludedInQuery(queryProps, editor.fsPath)); - queryProps.extraFileResources = openEditorsInQuery; - queryProps.folderQueries = []; - } - else { - // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace - const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); - queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; + const openEditorIncludes = openEditorsInQuery.map(editor => { + const workspace = this.workspaceContextService.getWorkspaceFolder(editor); + if (workspace) { + const relPath = path.relative(workspace?.uri.fsPath, editor.fsPath); + return includeFolderName ? `./${workspace.name}/${relPath}` : `${relPath}`; + } + else { + return editor.fsPath.replace(/^\//, ''); + } + }); + return this.commonQuery(folderResources, { + ...options, + onlyOpenEditors: false, + includePattern: openEditorIncludes, + excludePattern: openEditorIncludes.length + ? options.excludePattern + : '**/*' // when there are no included editors, explicitly exclude all other files + }, true); } + // Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace + const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath)); + queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined; + return queryProps; } @@ -236,11 +254,11 @@ export class QueryBuilder { /** * Take the includePattern as seen in the search viewlet, and split into components that look like searchPaths, and - * glob patterns. Glob patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}. + * glob patterns. When `strict` is false, patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}. * * Public for test. */ - parseSearchPaths(pattern: string | string[]): ISearchPathsInfo { + parseSearchPaths(pattern: string | string[], strict = false): ISearchPathsInfo { const isSearchPath = (segment: string) => { // A segment is a search path if it is an absolute path or starts with ./, ../, .\, or ..\ return path.isAbsolute(segment) || /^\.\.?([\/\\]|$)/.test(segment); @@ -263,15 +281,15 @@ export class QueryBuilder { .map(s => strings.rtrim(s, '/')) .map(s => strings.rtrim(s, '\\')) .map(p => { - if (p[0] === '.') { + if (!strict && p[0] === '.') { p = '*' + p; // convert ".js" to "*.js" } - return expandGlobalGlob(p); + return strict ? [p] : expandGlobalGlob(p); }); const result: ISearchPathsInfo = {}; - const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || []); + const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || [], strict); if (searchPaths && searchPaths.length) { result.searchPaths = searchPaths; } @@ -294,7 +312,7 @@ export class QueryBuilder { /** * Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths */ - private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] { + private expandSearchPathPatterns(searchPaths: string[], strict: boolean): ISearchPathPattern[] { if (!searchPaths || !searchPaths.length) { // No workspace => ignore search paths return []; @@ -314,7 +332,7 @@ export class QueryBuilder { // Expanded search paths to multiple resolved patterns (with ** and without) return arrays.flatten( - oneExpanded.map(oneExpandedResult => this.resolveOneSearchPathPattern(oneExpandedResult, globPortion))); + oneExpanded.map(oneExpandedResult => this.resolveOneSearchPathPattern(oneExpandedResult, globPortion, strict))); })); const searchPathPatternMap = new Map(); @@ -400,7 +418,7 @@ export class QueryBuilder { return []; } - private resolveOneSearchPathPattern(oneExpandedResult: IOneSearchPathPattern, globPortion?: string): IOneSearchPathPattern[] { + private resolveOneSearchPathPattern(oneExpandedResult: IOneSearchPathPattern, globPortion: string | undefined, strict: boolean): IOneSearchPathPattern[] { const pattern = oneExpandedResult.pattern && globPortion ? `${oneExpandedResult.pattern}/${globPortion}` : oneExpandedResult.pattern || globPortion; @@ -411,7 +429,7 @@ export class QueryBuilder { pattern }]; - if (pattern && !pattern.endsWith('**')) { + if (!strict && pattern && !pattern.endsWith('**')) { results.push({ searchPath: oneExpandedResult.searchPath, pattern: pattern + '/**' diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 4ed641bb79fef..ad4ddf97f6776 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -412,21 +412,25 @@ export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: return false; } - if (queryProps.includePattern && !glob.match(queryProps.includePattern, fsPath)) { - return false; - } + if (queryProps.includePattern || queryProps.usingSearchPaths) { + if (queryProps.includePattern && glob.match(queryProps.includePattern, fsPath)) { + return true; + } - // If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present - if (queryProps.usingSearchPaths) { - return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => { - const searchPath = fq.folder.fsPath; - if (extpath.isEqualOrParent(fsPath, searchPath)) { - const relPath = relative(searchPath, fsPath); - return !fq.includePattern || !!glob.match(fq.includePattern, relPath); - } else { - return false; - } - }); + // If searchPaths are being used, the extra file must be in a subfolder and match the pattern, if present + if (queryProps.usingSearchPaths) { + return !!queryProps.folderQueries && queryProps.folderQueries.some(fq => { + const searchPath = fq.folder.fsPath; + if (extpath.isEqualOrParent(fsPath, searchPath)) { + const relPath = relative(searchPath, fsPath); + return !fq.includePattern || !!glob.match(fq.includePattern, relPath); + } else { + return false; + } + }); + } + + return false; } return true; diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 074d3528f2947..85d6d3e96c744 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -42,6 +42,9 @@ export class RipgrepTextSearchEngine { const cwd = options.folder.fsPath; + console.log({ query, options, rgArgs, cwd }); + + const escapedArgs = rgArgs .map(arg => arg.match(/^-/) ? arg : `'${arg}'`) .join(' '); @@ -385,13 +388,7 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[] if (otherIncludes && otherIncludes.length) { const uniqueOthers = new Set(); - otherIncludes.forEach(other => { - if (!other.endsWith('/**')) { - other += '/**'; - } - - uniqueOthers.add(other); - }); + otherIncludes.forEach(other => { uniqueOthers.add(other); }); args.push('-g', '!*'); uniqueOthers @@ -508,10 +505,6 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[] */ export function spreadGlobComponents(globArg: string): string[] { const components = splitGlobAware(globArg, '/'); - if (components[components.length - 1] !== '**') { - components.push('**'); - } - return components.map((_, i) => components.slice(0, i + 1).join('/')); } From 2bbf639c258df61102642d851fb8e79314cee65b Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 11:01:09 -0800 Subject: [PATCH 06/11] Clean --- .../workbench/contrib/search/common/queryBuilder.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index ce9321c5156a7..879bbbed000e9 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -169,14 +169,16 @@ export class QueryBuilder { // Build folderQueries from searchPaths, if given, otherwise folderResources const includeFolderName = folderResources.length > 1; - const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? - includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : - folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) - .filter(query => !!query) as IFolderQuery[]; + const folderQueries = options.onlyOpenEditors + ? [] + : (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? + includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : + folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) + .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { _reason: options._reason, - folderQueries: options.onlyOpenEditors ? [] : folderQueries, + folderQueries, usingSearchPaths: !!(includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length), extraFileResources: options.extraFileResources, From 55c5382f0e3a218737efe16bec712c3ea38969c0 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 11:03:10 -0800 Subject: [PATCH 07/11] Remove log --- .../workbench/services/search/node/ripgrepTextSearchEngine.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 85d6d3e96c744..da0483de317d2 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -42,9 +42,6 @@ export class RipgrepTextSearchEngine { const cwd = options.folder.fsPath; - console.log({ query, options, rgArgs, cwd }); - - const escapedArgs = rgArgs .map(arg => arg.match(/^-/) ? arg : `'${arg}'`) .join(' '); From 5325766ad5f42c0fcad09f40ecc462518016dbd6 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 11:19:22 -0800 Subject: [PATCH 08/11] Naming --- .../contrib/search/common/queryBuilder.ts | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index 879bbbed000e9..7bb48f50172d7 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -162,19 +162,17 @@ export class QueryBuilder { this.parseSearchPaths(pattern, expandPatterns === 'strict'); } - private commonQuery(folderResources: (IWorkspaceFolderData | URI)[] = [], options: ICommonQueryBuilderOptions = {}, strict?: boolean): ICommonQueryProps { - const patternExpansionMode = strict ? 'strict' : options.expandPatterns ? 'loose' : 'none'; + private commonQuery(folderResources: (IWorkspaceFolderData | URI)[] = [], options: ICommonQueryBuilderOptions = {}, strictPatterns?: boolean): ICommonQueryProps { + const patternExpansionMode = strictPatterns ? 'strict' : options.expandPatterns ? 'loose' : 'none'; const includeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.includePattern, patternExpansionMode); const excludeSearchPathsInfo: ISearchPathsInfo = this.handleIncludeExclude(options.excludePattern, patternExpansionMode); // Build folderQueries from searchPaths, if given, otherwise folderResources const includeFolderName = folderResources.length > 1; - const folderQueries = options.onlyOpenEditors - ? [] - : (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? - includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : - folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) - .filter(query => !!query) as IFolderQuery[]; + const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? + includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : + folderResources.map(folder => this.getFolderQueryForRoot(folder, options, excludeSearchPathsInfo, includeFolderName))) + .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { _reason: options._reason, @@ -256,11 +254,11 @@ export class QueryBuilder { /** * Take the includePattern as seen in the search viewlet, and split into components that look like searchPaths, and - * glob patterns. When `strict` is false, patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}. + * glob patterns. When `strictPatterns` is false, patterns are expanded from 'foo/bar' to '{foo/bar/**, **\/foo/bar}. * * Public for test. */ - parseSearchPaths(pattern: string | string[], strict = false): ISearchPathsInfo { + parseSearchPaths(pattern: string | string[], strictPatterns = false): ISearchPathsInfo { const isSearchPath = (segment: string) => { // A segment is a search path if it is an absolute path or starts with ./, ../, .\, or ..\ return path.isAbsolute(segment) || /^\.\.?([\/\\]|$)/.test(segment); @@ -283,15 +281,15 @@ export class QueryBuilder { .map(s => strings.rtrim(s, '/')) .map(s => strings.rtrim(s, '\\')) .map(p => { - if (!strict && p[0] === '.') { + if (!strictPatterns && p[0] === '.') { p = '*' + p; // convert ".js" to "*.js" } - return strict ? [p] : expandGlobalGlob(p); + return strictPatterns ? [p] : expandGlobalGlob(p); }); const result: ISearchPathsInfo = {}; - const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || [], strict); + const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || [], strictPatterns); if (searchPaths && searchPaths.length) { result.searchPaths = searchPaths; } @@ -314,7 +312,7 @@ export class QueryBuilder { /** * Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths */ - private expandSearchPathPatterns(searchPaths: string[], strict: boolean): ISearchPathPattern[] { + private expandSearchPathPatterns(searchPaths: string[], strictPatterns: boolean): ISearchPathPattern[] { if (!searchPaths || !searchPaths.length) { // No workspace => ignore search paths return []; @@ -334,7 +332,7 @@ export class QueryBuilder { // Expanded search paths to multiple resolved patterns (with ** and without) return arrays.flatten( - oneExpanded.map(oneExpandedResult => this.resolveOneSearchPathPattern(oneExpandedResult, globPortion, strict))); + oneExpanded.map(oneExpandedResult => this.resolveOneSearchPathPattern(oneExpandedResult, globPortion, strictPatterns))); })); const searchPathPatternMap = new Map(); @@ -420,7 +418,7 @@ export class QueryBuilder { return []; } - private resolveOneSearchPathPattern(oneExpandedResult: IOneSearchPathPattern, globPortion: string | undefined, strict: boolean): IOneSearchPathPattern[] { + private resolveOneSearchPathPattern(oneExpandedResult: IOneSearchPathPattern, globPortion: string | undefined, strictPatterns: boolean): IOneSearchPathPattern[] { const pattern = oneExpandedResult.pattern && globPortion ? `${oneExpandedResult.pattern}/${globPortion}` : oneExpandedResult.pattern || globPortion; @@ -431,7 +429,7 @@ export class QueryBuilder { pattern }]; - if (!strict && pattern && !pattern.endsWith('**')) { + if (!strictPatterns && pattern && !pattern.endsWith('**')) { results.push({ searchPath: oneExpandedResult.searchPath, pattern: pattern + '/**' From 0b33708a4a33e701211baea1dc53b48c835b254d Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 11:52:09 -0800 Subject: [PATCH 09/11] Transfer open editors config to search editor --- src/vs/workbench/contrib/search/browser/searchView.ts | 4 ++-- src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts | 1 + .../contrib/searchEditor/browser/searchEditorActions.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 89fd017aa68db..d905ea2bde093 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -388,7 +388,7 @@ export class SearchView extends ViewPane { return this.searchWidget; } - get searchIncludePattern(): PatternInputWidget { + get searchIncludePattern(): IncludePatternInputWidget { return this.inputPatternIncludes; } @@ -1617,7 +1617,7 @@ export class SearchView extends ViewPane { this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); + this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.searchIncludePattern.onlySearchInOpenEditors()); })); this.reLayout(); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 0927118a7c021..b4282997499db 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -578,6 +578,7 @@ export class SearchEditor extends BaseTextEditor { 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.onlyOpenEditors !== undefined) { this.inputPatternIncludes.setOnlySearchInOpenEditors(config.onlyOpenEditors); } if (config.useExcludeSettingsAndIgnoreFiles !== undefined) { this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(config.useExcludeSettingsAndIgnoreFiles); } if (config.showIncludesExcludes !== undefined) { this.toggleIncludesExcludes(config.showIncludesExcludes); } } diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 7508c1e938fe7..f8aa2378e4fb4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -82,6 +82,7 @@ export async function openSearchEditor(accessor: ServicesAccessor): Promise { + async (accessor: ServicesAccessor, searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, onlySearchInOpenEditors: boolean) => { if (!searchResult.query) { console.error('Expected searchResult.query to be defined. Got', searchResult); return; @@ -180,6 +181,7 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); const { text, matchRanges, config } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, sortOrder); + config.onlyOpenEditors = onlySearchInOpenEditors; const contextLines = configurationService.getValue('search').searchEditor.defaultNumberOfContextLines; if (searchResult.isDirty || contextLines === 0 || contextLines === null) { From 6547307b709ff08a177fabc51c05f46adc6f0845 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 12:10:49 -0800 Subject: [PATCH 10/11] Pass in more places --- src/vs/workbench/contrib/search/browser/searchView.ts | 2 +- .../contrib/searchEditor/browser/searchEditor.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index d905ea2bde093..a466e6d107ebc 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -54,7 +54,7 @@ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/vie import { IEditorPane } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IViewDescriptorService } from 'vs/workbench/common/views'; -import { ExcludePatternInputWidget, IncludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; +import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; 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'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 047af4bc3cc84..b094c494f1e35 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -318,7 +318,7 @@ registerAction2(class extends Action2 { const instantiationService = accessor.get(IInstantiationService); const searchView = getSearchView(viewsService); if (searchView) { - await instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); + await instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), searchView.searchIncludePattern.onlySearchInOpenEditors()); } } }); From 7f752f66318451e15b76d80d5b70552a4ab46897 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 21 Jan 2021 16:30:17 -0800 Subject: [PATCH 11/11] Add some testing --- .../search/test/browser/queryBuilder.test.ts | 107 ++++++++++++++++-- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts index 6aae8a9309b83..f1ac5432f4d33 100644 --- a/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/queryBuilder.test.ts @@ -571,15 +571,42 @@ suite('QueryBuilder', () => { ].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(includePattern, expectedPatterns)); }); - function testIncludes(includePattern: string, expectedResult: ISearchPathsInfo): void { + test('strict includes', () => { + function testSimpleIncludes(includePattern: string, expectedPatterns: string[]): void { + assert.deepEqual( + queryBuilder.parseSearchPaths(includePattern, true), + { + pattern: patternsToIExpression(...expectedPatterns) + }, + includePattern); + } + + [ + ['a', ['a']], + ['a/b', ['a/b']], + ['a/b, c', ['a/b', 'c']], + ['a,.txt', ['a', '.txt']], + ['a,,,b', ['a', 'b']], + ['**/a,b/**', ['**/a', 'b/**']] + ].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(includePattern, expectedPatterns)); + }); + + function testIncludes(includePattern: string, expectedResultLoose: ISearchPathsInfo, expectedResultStrict?: ISearchPathsInfo): void { assertEqualSearchPathResults( queryBuilder.parseSearchPaths(includePattern), - expectedResult, + expectedResultLoose, includePattern); + + if (expectedResultStrict) { + assertEqualSearchPathResults( + queryBuilder.parseSearchPaths(includePattern, true), + expectedResultStrict, + includePattern); + } } - function testIncludesDataItem([includePattern, expectedResult]: [string, ISearchPathsInfo]): void { - testIncludes(includePattern, expectedResult); + function testIncludesDataItem([includePattern, expectedResultLoose, expectedResultStrict]: [string, ISearchPathsInfo, ISearchPathsInfo] | [string, ISearchPathsInfo]): void { + testIncludes(includePattern, expectedResultLoose, expectedResultStrict); } test('absolute includes', () => { @@ -652,7 +679,7 @@ suite('QueryBuilder', () => { }); test('relative includes w/single root folder', () => { - const cases: [string, ISearchPathsInfo][] = [ + const cases: ([string, ISearchPathsInfo] | [string, ISearchPathsInfo, ISearchPathsInfo])[] = [ [ './a', { @@ -660,6 +687,12 @@ suite('QueryBuilder', () => { searchPath: ROOT_1_URI, pattern: patternsToIExpression('a', 'a/**') }] + }, + { + searchPaths: [{ + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('a') + }] } ], [ @@ -669,6 +702,12 @@ suite('QueryBuilder', () => { searchPath: ROOT_1_URI, pattern: patternsToIExpression('a', 'a/**') }] + }, + { + searchPaths: [{ + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('a') + }] } ], [ @@ -700,6 +739,12 @@ suite('QueryBuilder', () => { searchPath: ROOT_1_URI, pattern: patternsToIExpression('a/b', 'a/b/**', 'c/d', 'c/d/**') }] + }, + { + searchPaths: [{ + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('a/b', 'c/d',) + }] } ], [ @@ -735,9 +780,14 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH, extUriBiasedIgnorePathCase); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsInfo][] = [ + const cases: ([string, ISearchPathsInfo] | [string, ISearchPathsInfo, ISearchPathsInfo])[] = [ [ './root1', + { + searchPaths: [{ + searchPath: getUri(ROOT_1) + }] + }, { searchPaths: [{ searchPath: getUri(ROOT_1) @@ -746,12 +796,42 @@ suite('QueryBuilder', () => { ], [ './root2', + { + searchPaths: [{ + searchPath: getUri(ROOT_2), + }] + }, { searchPaths: [{ searchPath: getUri(ROOT_2), }] } ], + [ + './root1/a/b, ./root2/a.txt', + { + searchPaths: [ + { + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('a/b', 'a/b/**') + }, + { + searchPath: getUri(ROOT_2), + pattern: patternsToIExpression('a.txt', 'a.txt/**') + }] + }, + { + searchPaths: [ + { + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('a/b') + }, + { + searchPath: getUri(ROOT_2), + pattern: patternsToIExpression('a.txt') + }] + } + ], [ './root1/a/**/b, ./root2/**/*.txt', { @@ -776,7 +856,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH, extUriBiasedIgnorePathCase); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsInfo][] = [ + const cases: ([string, ISearchPathsInfo] | [string, ISearchPathsInfo, ISearchPathsInfo])[] = [ [ './foldername', { @@ -792,6 +872,12 @@ suite('QueryBuilder', () => { searchPath: ROOT_1_URI, pattern: patternsToIExpression('foo', 'foo/**') }] + }, + { + searchPaths: [{ + searchPath: ROOT_1_URI, + pattern: patternsToIExpression('foo', 'foo') + }] } ] ]; @@ -804,7 +890,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }], WS_CONFIG_PATH, extUriBiasedIgnorePathCase); mockWorkspace.configuration = uri.file(fixPath('/config')); - const cases: [string, ISearchPathsInfo][] = [ + const cases: ([string, ISearchPathsInfo] | [string, ISearchPathsInfo, ISearchPathsInfo])[] = [ [ '', { @@ -819,6 +905,11 @@ suite('QueryBuilder', () => { ], [ './root1', + { + searchPaths: [{ + searchPath: getUri(ROOT_1) + }] + }, { searchPaths: [{ searchPath: getUri(ROOT_1)