From 501085f224933a62bdabc2e2248f8770f33fa7cb Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Mon, 13 Jan 2020 21:51:16 -0800 Subject: [PATCH] Add basic save functionality to search editor --- .../search/browser/search.contribution.ts | 5 +- .../contrib/search/browser/searchEditor.ts | 21 ++- .../search/browser/searchEditorCommands.ts | 121 ++++++++++++------ 3 files changed, 107 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3e698a8a74c..9e4d6ce61d8 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -57,7 +57,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { SearchEditorInput, SearchEditorInputFactory } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; +import { SearchEditorInput, SearchEditorInputFactory, SearchEditorContribution } from 'vs/workbench/contrib/search/browser/searchEditorCommands'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; @@ -909,3 +909,6 @@ Registry.as(EditorExtensions.Editors).registerEditor( Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( SearchEditorInput.ID, SearchEditorInputFactory); + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(SearchEditorContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 505ea541890..26bc97814d1 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -158,6 +158,8 @@ export class SearchEditor extends BaseEditor { } } }); + + this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); } private async runSearch(instant = false) { @@ -225,14 +227,28 @@ export class SearchEditor extends BaseEditor { (assertIsDefined(this._input) as SearchEditorInput).setConfig(config); const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); + const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, true); const textModel = assertIsDefined(this.searchResultEditor.getModel()); textModel.setValue(results.text.join(lineDelimiter)); + this.hideHeader(); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); searchModel.dispose(); } + private hideHeader() { + const headerLines = + this.searchResultEditor + .getModel() + ?.getValueInRange(new Range(1, 1, 6, 1)) + .split('\n') + .filter(line => line.startsWith('#')) + .length + ?? 0; + + this.searchResultEditor.setHiddenAreas([new Range(1, 1, headerLines + 1, 1)]); + } + layout(dimension: DOM.Dimension) { this.dimension = dimension; this.reLayout(); @@ -255,8 +271,7 @@ export class SearchEditor extends BaseEditor { if (!(newInput instanceof SearchEditorInput)) { return; } this.pauseSearching = true; // TODO: Manage model lifecycle in SearchEditorInput - const model = this.modelService.getModel(newInput.getResource()); - + const model = this.modelService.getModel(newInput.resource); this.searchResultEditor.setModel(model); this.queryEditorWidget.setValue(newInput.config.query, true); this.queryEditorWidget.searchInput.setCaseSensitive(newInput.config.caseSensitive); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index 07177d95ea8..c0caeedac62 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -5,13 +5,13 @@ import { coalesce, flatten } from 'vs/base/common/arrays'; import * as network from 'vs/base/common/network'; -import { repeat } from 'vs/base/common/strings'; +import { repeat, endsWith } from 'vs/base/common/strings'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; -import { EndOfLinePreference, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -25,16 +25,22 @@ import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/co import { FileMatch, Match, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPatternInfo, ISearchConfigurationProperties, ITextQuery } from 'vs/workbench/services/search/common/search'; -import { EditorInput, IEditorInputFactory } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, GroupIdentifier, EditorInput } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +// import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; + export type SearchConfiguration = { query: string, includes: string, - excludes: string, + excludes: string contextLines: number, wholeWord: boolean, caseSensitive: boolean, @@ -43,6 +49,27 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +export class SearchEditorContribution implements IWorkbenchContribution { + // constructor( + // @IEditorService private readonly editorService: IEditorService, + // @ITextFileService protected readonly textFileService: ITextFileService, + // ) { + // this.editorService.overrideOpenEditor(async (editor, options, group) => { + // const resource = editor.getResource(); + // if (!resource || !endsWith(resource.path, '.code-search') || !(editor instanceof FileEditorInput)) { + // return undefined; + // } + + // console.log('hello agian'); + // const contents = await this.textFileService.read(resource); + // contents.value; + + // return undefined; + + // }); + // } +} + export class SearchEditorInputFactory implements IEditorInputFactory { canSerialize() { return true; } @@ -52,7 +79,7 @@ export class SearchEditorInputFactory implements IEditorInputFactory { } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput)); + return instantiationService.createInstance(SearchEditorInput, JSON.parse(serializedEditorInput), undefined); } } @@ -60,40 +87,56 @@ let searchEditorInputInstances = 0; export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; - public config: SearchConfiguration; - private instanceNumber: number = searchEditorInputInstances++; + private _config: SearchConfiguration; + public get config(): Readonly { + return this._config; + } + + private model: ITextModel; + public readonly resource: URI; constructor( config: SearchConfiguration | undefined, + initialContents: string | undefined, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, + @IEditorService protected readonly editorService: IEditorService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, + @ITextFileService protected readonly textFileService: ITextFileService, + @IUntitledTextEditorService protected readonly untitledTextEditorService: IUntitledTextEditorService, ) { super(); + this.resource = URI.from({ scheme: 'search-editor', fragment: `${searchEditorInputInstances++}` }); + if (config === undefined) { - this.config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; + this._config = { query: '', includes: '', excludes: '', contextLines: 0, wholeWord: false, caseSensitive: false, regexp: false, useIgnores: true, showIncludesExcludes: false }; } else { - this.config = config; + this._config = config; } const searchResultMode = this.modeService.create('search-result'); - this.modelService.createModel('', searchResultMode, this.getResource()); + + this.model = this.modelService.createModel(initialContents ?? '', searchResultMode, this.resource); + } + + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + const path = (this.config.query.replace(/\W/g, '') || 'Untitled Search') + '.code-search'; + const untitledResource = URI.from({ scheme: 'untitled', path, fragment: this.resource.fragment }); + const a = this.untitledTextEditorService.createOrGet(untitledResource, 'search-result', this.model.getValue()); + return a.save(group, options).finally(() => a.dispose()); } getTypeId(): string { return SearchEditorInput.ID; } - getResource(): URI { - return URI.from({ scheme: 'search-editor', fragment: `${this.instanceNumber}` }); - } - getName(): string { return this.config.query ? localize('searchTitle.withQuery', "Search: {0}", this.config.query) : localize('searchTitle', "Search"); } setConfig(config: SearchConfiguration) { - this.config = config; + this._config = config; this._onDidChangeLabel.fire(); } @@ -102,9 +145,17 @@ export class SearchEditorInput extends EditorInput { } dispose() { - this.modelService.destroyModel(this.getResource()); + this.modelService.destroyModel(this.resource); super.dispose(); } + + matches(other: unknown) { + if (this === other) { return true; } + if (other instanceof UntitledTextEditorInput) { + if (other.getResource().fragment === this.resource.fragment) { return true; } + } + return false; + } } // Using \r\n on Windows inserts an extra newline between results. @@ -313,7 +364,7 @@ export const serializeSearchResultForEditor = (searchResult: SearchResult, rawIn .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); - return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; + return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text.length ? allResults.text : ['No Results']) }; }; export const refreshActiveEditorSearch = @@ -373,7 +424,7 @@ export const refreshActiveEditorSearch = await searchModel.search(query); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, false); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter, true); textModel.setValue(results.text.join(lineDelimiter)); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); @@ -381,7 +432,7 @@ export const refreshActiveEditorSearch = export const openNewSearchEditor = async (editorService: IEditorService, instantiationService: IInstantiationService) => { - await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined), { pinned: true }); + await editorService.openEditor(instantiationService.createInstance(SearchEditorInput, undefined, undefined), { pinned: true }); }; export const createEditorFromSearchResult = @@ -395,7 +446,7 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, false); + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); const contents = results.text.join(lineDelimiter); let possible = { contents, @@ -418,24 +469,22 @@ export const createEditorFromSearchResult = existing = editorService.getOpened(possible); } - const editor = await editorService.openEditor( - instantiationService.createInstance( - SearchEditorInput, - { - query: searchResult.query.contentPattern.pattern, - regexp: !!searchResult.query.contentPattern.isRegExp, - caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, - wholeWord: !!searchResult.query.contentPattern.isWordMatch, - includes: rawIncludePattern, - excludes: rawExcludePattern, - contextLines: 0, - useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, - showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) - }), - { pinned: true }) as SearchEditor; + const input = instantiationService.createInstance( + SearchEditorInput, + { + query: searchResult.query.contentPattern.pattern, + regexp: !!searchResult.query.contentPattern.isRegExp, + caseSensitive: !!searchResult.query.contentPattern.isCaseSensitive, + wholeWord: !!searchResult.query.contentPattern.isWordMatch, + includes: rawIncludePattern, + excludes: rawExcludePattern, + contextLines: 0, + useIgnores: !searchResult.query.userDisabledExcludesAndIgnoreFiles, + showIncludesExcludes: !!(rawExcludePattern || rawExcludePattern || searchResult.query.userDisabledExcludesAndIgnoreFiles) + }, contents); + const editor = await editorService.openEditor(input, { pinned: true }) as SearchEditor; const model = assertIsDefined(editor.getModel()); - model.setValue(contents); model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); };