Skip to content

Commit f56d7fc

Browse files
author
Jackson Kearl
committed
Initial work on "search in open editors"
1 parent f9109f4 commit f56d7fc

File tree

8 files changed

+113
-11
lines changed

8 files changed

+113
-11
lines changed

src/vs/workbench/contrib/search/browser/patternInputWidget.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedH
1818
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1919
import type { IThemable } from 'vs/base/common/styler';
2020
import { Codicon } from 'vs/base/common/codicons';
21-
21+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
22+
import { ISearchConfiguration } from 'vs/workbench/services/search/common/search';
2223
export interface IOptions {
2324
placeholder?: string;
2425
width?: number;
@@ -50,7 +51,8 @@ export class PatternInputWidget extends Widget implements IThemable {
5051

5152
constructor(parent: HTMLElement, private contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
5253
@IThemeService protected themeService: IThemeService,
53-
@IContextKeyService private readonly contextKeyService: IContextKeyService
54+
@IContextKeyService private readonly contextKeyService: IContextKeyService,
55+
@IConfigurationService protected readonly configurationService: IConfigurationService
5456
) {
5557
super();
5658
this.width = options.width || 100;
@@ -178,16 +180,73 @@ export class PatternInputWidget extends Widget implements IThemable {
178180
}
179181
}
180182

183+
export class IncludePatternInputWidget extends PatternInputWidget {
184+
185+
private _onChangeSearchInEditorsBoxEmitter = this._register(new Emitter<void>());
186+
onChangeSearchInEditorsBox = this._onChangeSearchInEditorsBoxEmitter.event;
187+
188+
constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
189+
@IThemeService themeService: IThemeService,
190+
@IContextKeyService contextKeyService: IContextKeyService,
191+
@IConfigurationService configurationService: IConfigurationService,
192+
) {
193+
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
194+
}
195+
196+
private useSearchInEditorsBox!: Checkbox;
197+
198+
dispose(): void {
199+
super.dispose();
200+
this.useSearchInEditorsBox.dispose();
201+
}
202+
203+
onlySearchInOpenEditors(): boolean {
204+
return this.useSearchInEditorsBox.checked;
205+
}
206+
207+
setOnlySearchInOpenEditors(value: boolean) {
208+
this.useSearchInEditorsBox.checked = value;
209+
}
210+
211+
protected getSubcontrolsWidth(): number {
212+
if (this.configurationService.getValue<ISearchConfiguration>().search?.experimental?.searchInOpenEditors) {
213+
return super.getSubcontrolsWidth() + this.useSearchInEditorsBox.width();
214+
}
215+
return super.getSubcontrolsWidth();
216+
}
217+
218+
protected renderSubcontrols(controlsDiv: HTMLDivElement): void {
219+
this.useSearchInEditorsBox = this._register(new Checkbox({
220+
icon: Codicon.book,
221+
title: nls.localize('onlySearchInOpenEditors', "Search Only in Open Editors"),
222+
isChecked: false,
223+
}));
224+
if (!this.configurationService.getValue<ISearchConfiguration>().search?.experimental?.searchInOpenEditors) {
225+
return;
226+
}
227+
this._register(this.useSearchInEditorsBox.onChange(viaKeyboard => {
228+
this._onChangeSearchInEditorsBoxEmitter.fire();
229+
if (!viaKeyboard) {
230+
this.inputBox.focus();
231+
}
232+
}));
233+
this._register(attachCheckboxStyler(this.useSearchInEditorsBox, this.themeService));
234+
controlsDiv.appendChild(this.useSearchInEditorsBox.domNode);
235+
super.renderSubcontrols(controlsDiv);
236+
}
237+
}
238+
181239
export class ExcludePatternInputWidget extends PatternInputWidget {
182240

183241
private _onChangeIgnoreBoxEmitter = this._register(new Emitter<void>());
184242
onChangeIgnoreBox = this._onChangeIgnoreBoxEmitter.event;
185243

186244
constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options: IOptions = Object.create(null),
187245
@IThemeService themeService: IThemeService,
188-
@IContextKeyService contextKeyService: IContextKeyService
246+
@IContextKeyService contextKeyService: IContextKeyService,
247+
@IConfigurationService configurationService: IConfigurationService,
189248
) {
190-
super(parent, contextViewProvider, options, themeService, contextKeyService);
249+
super(parent, contextViewProvider, options, themeService, contextKeyService, configurationService);
191250
}
192251

193252
private useExcludesAndIgnoreFilesBox!: Checkbox;

src/vs/workbench/contrib/search/browser/search.contribution.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,11 @@ configurationRegistry.registerConfiguration({
998998
],
999999
'description': nls.localize('search.sortOrder', "Controls sorting order of search results.")
10001000
},
1001+
'search.experimental.searchInOpenEditors': {
1002+
type: 'boolean',
1003+
default: false,
1004+
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.**")
1005+
}
10011006
}
10021007
});
10031008

src/vs/workbench/contrib/search/browser/searchView.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/vie
5454
import { IEditorPane } from 'vs/workbench/common/editor';
5555
import { Memento, MementoObject } from 'vs/workbench/common/memento';
5656
import { IViewDescriptorService } from 'vs/workbench/common/views';
57-
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
57+
import { ExcludePatternInputWidget, IncludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
5858
import { appendKeyBindingLabel, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
5959
import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
6060
import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView';
@@ -125,7 +125,7 @@ export class SearchView extends ViewPane {
125125
private queryDetails!: HTMLElement;
126126
private toggleQueryDetailsButton!: HTMLElement;
127127
private inputPatternExcludes!: ExcludePatternInputWidget;
128-
private inputPatternIncludes!: PatternInputWidget;
128+
private inputPatternIncludes!: IncludePatternInputWidget;
129129
private resultsElement!: HTMLElement;
130130

131131
private currentSelectedFileMatch: FileMatch | undefined;
@@ -309,14 +309,17 @@ export class SearchView extends ViewPane {
309309
const filesToIncludeTitle = nls.localize('searchScope.includes', "files to include");
310310
dom.append(folderIncludesList, $('h4', undefined, filesToIncludeTitle));
311311

312-
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
312+
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, {
313313
ariaLabel: nls.localize('label.includes', 'Search Include Patterns'),
314314
history: patternIncludesHistory,
315315
}));
316316

317317
this.inputPatternIncludes.setValue(patternIncludes);
318318

319+
this._register(this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerQueryChange({ triggeredOnType, delay: this.searchConfig.searchOnTypeDebouncePeriod })));
319320
this._register(this.inputPatternIncludes.onCancel(() => this.cancelSearch(false)));
321+
this._register(this.inputPatternIncludes.onChangeSearchInEditorsBox(() => this.triggerQueryChange()));
322+
320323
this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused);
321324

322325
// excludes list
@@ -1293,6 +1296,7 @@ export class SearchView extends ViewPane {
12931296
const excludePatternText = this.inputPatternExcludes.getValue().trim();
12941297
const includePatternText = this.inputPatternIncludes.getValue().trim();
12951298
const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
1299+
const onlySearchInOpenEditors = this.inputPatternIncludes.onlySearchInOpenEditors();
12961300

12971301
if (contentPattern.length === 0) {
12981302
this.clearSearchResults(false);
@@ -1321,6 +1325,7 @@ export class SearchView extends ViewPane {
13211325
maxResults: SearchView.MAX_TEXT_RESULTS,
13221326
disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined,
13231327
disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined,
1328+
onlyOpenEditors: onlySearchInOpenEditors,
13241329
excludePattern,
13251330
includePattern,
13261331
previewOptions: {

src/vs/workbench/contrib/search/common/queryBuilder.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch';
1616
import * as nls from 'vs/nls';
1717
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1818
import { IWorkspaceContextService, IWorkspaceFolderData, toWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
19+
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
1920
import { IPathService } from 'vs/workbench/services/path/common/pathService';
2021
import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search';
2122

@@ -59,6 +60,7 @@ export interface ICommonQueryBuilderOptions {
5960
disregardExcludeSettings?: boolean;
6061
disregardSearchExcludeSettings?: boolean;
6162
ignoreSymlinks?: boolean;
63+
onlyOpenEditors?: boolean;
6264
}
6365

6466
export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions {
@@ -81,6 +83,7 @@ export class QueryBuilder {
8183
constructor(
8284
@IConfigurationService private readonly configurationService: IConfigurationService,
8385
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
86+
@IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
8487
@IPathService private readonly pathService: IPathService
8588
) {
8689
}
@@ -178,9 +181,26 @@ export class QueryBuilder {
178181

179182
excludePattern: excludeSearchPathsInfo.pattern,
180183
includePattern: includeSearchPathsInfo.pattern,
184+
onlyOpenEditors: options.onlyOpenEditors,
181185
maxResults: options.maxResults
182186
};
183187

188+
if (options.onlyOpenEditors) {
189+
const openEditors = arrays.coalesce(arrays.flatten(this.editorGroupsService.groups.map(group => group.editors.map(editor => editor.resource))));
190+
const openEditorsInQuery = openEditors.filter(editor => pathIncludedInQuery(queryProps, editor.fsPath));
191+
const openEditorIncludes = openEditorsInQuery.map(editor => {
192+
const workspace = this.workspaceContextService.getWorkspaceFolder(editor);
193+
if (workspace) {
194+
const relPath = path.relative(workspace?.uri.fsPath, editor.fsPath);
195+
return includeFolderName ? `./${workspace.name}/${relPath}` : `./${relPath}`;
196+
}
197+
else {
198+
return editor.fsPath.replace(/^\//, '');
199+
}
200+
});
201+
// return this.commonQuery(folderResources, { ...options, expandPatterns: false, onlyOpenEditors: false, includePattern: openEditorIncludes.join(', ') });
202+
}
203+
184204
// Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace
185205
const extraFileResources = options.extraFileResources && options.extraFileResources.filter(extraFile => pathIncludedInQuery(queryProps, extraFile.fsPath));
186206
queryProps.extraFileResources = extraFileResources && extraFileResources.length ? extraFileResources : undefined;

src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor
3838
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
3939
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
4040
import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor';
41-
import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
41+
import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
4242
import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget';
4343
import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants';
4444
import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder';
@@ -67,7 +67,7 @@ export class SearchEditor extends BaseTextEditor {
6767
private searchResultEditor!: CodeEditorWidget;
6868
private queryEditorContainer!: HTMLElement;
6969
private dimension?: DOM.Dimension;
70-
private inputPatternIncludes!: PatternInputWidget;
70+
private inputPatternIncludes!: IncludePatternInputWidget;
7171
private inputPatternExcludes!: ExcludePatternInputWidget;
7272
private includesExcludesContainer!: HTMLElement;
7373
private toggleQueryDetailsButton!: HTMLElement;
@@ -168,10 +168,11 @@ export class SearchEditor extends BaseTextEditor {
168168
const folderIncludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.includes'));
169169
const filesToIncludeTitle = localize('searchScope.includes', "files to include");
170170
DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle));
171-
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, {
171+
this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, {
172172
ariaLabel: localize('label.includes', 'Search Include Patterns'),
173173
}));
174174
this.inputPatternIncludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 }));
175+
this._register(this.inputPatternIncludes.onChangeSearchInEditorsBox(() => this.triggerSearch()));
175176

176177
// // Excludes
177178
const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes'));
@@ -181,7 +182,7 @@ export class SearchEditor extends BaseTextEditor {
181182
ariaLabel: localize('label.excludes', 'Search Exclude Patterns'),
182183
}));
183184
this.inputPatternExcludes.onSubmit(triggeredOnType => this.triggerSearch({ resetCursor: false, delay: triggeredOnType ? this.searchConfig.searchOnTypeDebouncePeriod : 0 }));
184-
this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch());
185+
this._register(this.inputPatternExcludes.onChangeIgnoreBox(() => this.triggerSearch()));
185186

186187
[this.queryEditorWidget.searchInput, this.inputPatternIncludes, this.inputPatternExcludes].map(input =>
187188
this._register(attachInputBoxStyler(input, this.themeService, { inputBorder: searchEditorTextInputBorder })));
@@ -449,6 +450,7 @@ export class SearchEditor extends BaseTextEditor {
449450
isRegexp: this.queryEditorWidget.searchInput.getRegex(),
450451
matchWholeWord: this.queryEditorWidget.searchInput.getWholeWords(),
451452
useExcludeSettingsAndIgnoreFiles: this.inputPatternExcludes.useExcludesAndIgnoreFiles(),
453+
onlyOpenEditors: this.inputPatternIncludes.onlySearchInOpenEditors(),
452454
showIncludesExcludes: this.showingIncludesExcludes
453455
};
454456
}
@@ -483,6 +485,7 @@ export class SearchEditor extends BaseTextEditor {
483485
disregardExcludeSettings: !config.useExcludeSettingsAndIgnoreFiles || undefined,
484486
excludePattern: config.filesToExclude,
485487
includePattern: config.filesToInclude,
488+
onlyOpenEditors: config.onlyOpenEditors,
486489
previewOptions: {
487490
matchLines: 1,
488491
charsPerLine: 1000

src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type SearchConfiguration = {
4040
isRegexp: boolean,
4141
useExcludeSettingsAndIgnoreFiles: boolean,
4242
showIncludesExcludes: boolean,
43+
onlyOpenEditors: boolean,
4344
};
4445

4546
export const SEARCH_EDITOR_EXT = '.code-search';

src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const contentPatternToSearchConfiguration = (pattern: ITextQuery, includes: stri
115115
showIncludesExcludes: !!(includes || excludes || pattern?.userDisabledExcludesAndIgnoreFiles),
116116
useExcludeSettingsAndIgnoreFiles: (pattern?.userDisabledExcludesAndIgnoreFiles === undefined ? true : !pattern.userDisabledExcludesAndIgnoreFiles),
117117
contextLines,
118+
onlyOpenEditors: !!pattern.onlyOpenEditors,
118119
};
119120
};
120121

@@ -131,6 +132,7 @@ export const serializeSearchConfiguration = (config: Partial<SearchConfiguration
131132
config.isCaseSensitive && 'CaseSensitive',
132133
config.matchWholeWord && 'WordMatch',
133134
config.isRegexp && 'RegExp',
135+
config.onlyOpenEditors && 'OpenEditors',
134136
(config.useExcludeSettingsAndIgnoreFiles === false) && 'IgnoreExcludeSettings'
135137
]).join(' ')}`,
136138
config.filesToInclude ? `# Including: ${config.filesToInclude}` : undefined,
@@ -153,6 +155,7 @@ export const defaultSearchConfig = (): SearchConfiguration => ({
153155
matchWholeWord: false,
154156
contextLines: 0,
155157
showIncludesExcludes: false,
158+
onlyOpenEditors: false,
156159
});
157160

158161
export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguration => {
@@ -197,6 +200,7 @@ export const extractSearchQueryFromLines = (lines: string[]): SearchConfiguratio
197200
query.isCaseSensitive = value.indexOf('CaseSensitive') !== -1;
198201
query.useExcludeSettingsAndIgnoreFiles = value.indexOf('IgnoreExcludeSettings') === -1;
199202
query.matchWholeWord = value.indexOf('WordMatch') !== -1;
203+
query.onlyOpenEditors = value.indexOf('OpenEditors') !== -1;
200204
}
201205
}
202206
}

src/vs/workbench/services/search/common/search.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export interface ICommonQueryProps<U extends UriComponents> {
7777
excludePattern?: glob.IExpression;
7878
extraFileResources?: U[];
7979

80+
onlyOpenEditors?: boolean;
81+
8082
maxResults?: number;
8183
usingSearchPaths?: boolean;
8284
}
@@ -372,6 +374,9 @@ export interface ISearchConfigurationProperties {
372374
defaultNumberOfContextLines: number | null,
373375
experimental: {}
374376
};
377+
experimental: {
378+
searchInOpenEditors: boolean
379+
}
375380
sortOrder: SearchSortOrder;
376381
}
377382

0 commit comments

Comments
 (0)