From f895929a825b86e0584a0069bcbfead62cc0cedd Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 19 Oct 2022 12:22:37 -0700 Subject: [PATCH 01/35] initial marks --- .../workbench/contrib/notebook/browser/notebookEditorWidget.ts | 1 + src/vs/workbench/contrib/search/common/searchModel.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index f54262aa0b926..1143d9b628e93 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2384,6 +2384,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false): Promise { + // here if (!this._notebookViewModel) { return []; } diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index f5d7b7b8bd007..ccecc6dd3128c 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -31,6 +31,7 @@ import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +// import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -1067,6 +1068,7 @@ export class SearchResult extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + // @IEditorService private readonly editorService: IEditorService, ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); From 03ad46750156acb92aca6429a772f0f39f74b404 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 21 Oct 2022 12:46:08 -0700 Subject: [PATCH 02/35] initial changes to support notebook editor binding --- .../search/{common => browser}/replace.ts | 2 +- .../search/browser/replaceContributions.ts | 2 +- .../contrib/search/browser/replaceService.ts | 4 +- .../search/browser/search.contribution.ts | 2 +- .../contrib/search/browser/searchActions.ts | 4 +- .../search/{common => browser}/searchModel.ts | 106 +++++++++++++++++- .../search/browser/searchResultsView.ts | 2 +- .../contrib/search/browser/searchView.ts | 5 +- .../search/test/browser/mockSearchTree.ts | 2 +- .../search/test/browser/searchActions.test.ts | 2 +- .../{common => browser}/searchModel.test.ts | 2 +- .../{common => browser}/searchResult.test.ts | 4 +- .../search/test/browser/searchViewlet.test.ts | 2 +- .../textsearch.perf.integrationTest.ts | 2 +- .../searchEditor/browser/searchEditor.ts | 2 +- .../browser/searchEditorActions.ts | 2 +- .../browser/searchEditorSerialization.ts | 2 +- 17 files changed, 121 insertions(+), 26 deletions(-) rename src/vs/workbench/contrib/search/{common => browser}/replace.ts (97%) rename src/vs/workbench/contrib/search/{common => browser}/searchModel.ts (94%) rename src/vs/workbench/contrib/search/test/{common => browser}/searchModel.test.ts (99%) rename src/vs/workbench/contrib/search/test/{common => browser}/searchResult.test.ts (99%) diff --git a/src/vs/workbench/contrib/search/common/replace.ts b/src/vs/workbench/contrib/search/browser/replace.ts similarity index 97% rename from src/vs/workbench/contrib/search/common/replace.ts rename to src/vs/workbench/contrib/search/browser/replace.ts index 8c1320c1f9cc3..4c9949259ec85 100644 --- a/src/vs/workbench/contrib/search/common/replace.ts +++ b/src/vs/workbench/contrib/search/browser/replace.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Match, FileMatch, FileMatchOrMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { Match, FileMatch, FileMatchOrMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; diff --git a/src/vs/workbench/contrib/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts index 42176f2aeecb5..1fbf571ff2bbc 100644 --- a/src/vs/workbench/contrib/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index d653f37720370..9d7dced2e8b2f 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -7,11 +7,11 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as network from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; +import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 8efed0ce080cf..c8fe860bc22b1 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -47,7 +47,7 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { resolveResourcesForSearchIncludes } from 'vs/workbench/services/search/common/queryBuilder'; import { getWorkspaceSymbols, IWorkspaceSymbol, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, ISearchWorkbenchService, Match, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, ISearchWorkbenchService, Match, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 87ccd82a90a84..e2001a808ce8a 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -21,9 +21,9 @@ import { IViewsService } from 'vs/workbench/common/views'; import { searchRemoveIcon, searchReplaceAllIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { arrayContainsElementOrParent, FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWithResource, FolderMatchWorkspaceRoot, Match, RenderableMatch, searchComparer, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { arrayContainsElementOrParent, FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWithResource, FolderMatchWorkspaceRoot, Match, RenderableMatch, searchComparer, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { OpenEditorCommandId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts similarity index 94% rename from src/vs/workbench/contrib/search/common/searchModel.ts rename to src/vs/workbench/contrib/search/browser/searchModel.ts index ccecc6dd3128c..c8c779bb6d117 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -30,8 +30,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -// import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -207,6 +209,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _resource: URI; private _fileStat?: IFileStatWithPartialMetadata; private _model: ITextModel | null = null; + private _notebookEditorWidget: NotebookEditorWidget | null = null; private _modelListener: IDisposable | null = null; private _matches: Map; private _removedMatches: Set; @@ -271,6 +274,16 @@ export class FileMatch extends Disposable implements IFileMatch { this.updateHighlights(); } + bindEditorWidget(widget: NotebookEditorWidget) { + this._notebookEditorWidget = widget; + console.log(`added widget ${this._notebookEditorWidget.textModel?.uri}`); + } + + unbindEditorWidget(widget: NotebookEditorWidget) { + this._notebookEditorWidget = null; + console.log(`removed widget ${widget.textModel?.uri}`); + } + private onModelWillDispose(): void { // Update matches because model might have some dirty changes this.updateMatchesForModel(); @@ -573,6 +586,33 @@ export class FolderMatch extends Disposable { } } + bindEditorWidget(editor: NotebookEditorWidget, resource: URI) { + const fileMatch = this._fileMatches.get(resource); + + if (fileMatch) { + fileMatch.bindEditorWidget(editor); + } else { + const folderMatches = this.folderMatchesIterator(); + for (const elem of folderMatches) { + elem.bindEditorWidget(editor, resource); + } + } + } + + unbindEditorWidget(editor: NotebookEditorWidget, resource: URI) { + const fileMatch = this._fileMatches.get(resource); + + if (fileMatch) { + fileMatch.unbindEditorWidget(editor); + } else { + const folderMatches = this.folderMatchesIterator(); + for (const elem of folderMatches) { + elem.unbindEditorWidget(editor, resource); + } + } + + } + public createIntermediateFolderMatch(resource: URI, id: string, index: number, query: ITextQuery, baseWorkspaceFolder: FolderMatchWorkspaceRoot): FolderMatchWithResource { const folderMatch = this.instantiationService.createInstance(FolderMatchWithResource, resource, id, index, query, this, this._searchModel, baseWorkspaceFolder); this.configureIntermediateMatch(folderMatch); @@ -1056,10 +1096,9 @@ export class SearchResult extends Disposable { private _folderMatchesMap: TernarySearchTree = TernarySearchTree.forUris(key => this.uriIdentityService.extUri.ignorePathCasing(key)); private _showHighlights: boolean = false; private _query: ITextQuery | null = null; - private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; - + private _notebookEditors: Set; private _isDirty = false; constructor( @@ -1068,18 +1107,23 @@ export class SearchResult extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - // @IEditorService private readonly editorService: IEditorService, + @IEditorService private readonly editorService: IEditorService, ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); - + this._register(this.editorService.onDidVisibleEditorsChange(() => { + this.onDidVisibleEditorsChange(); + })); this._register(this.onChange(e => { if (e.removed) { this._isDirty = !this.isEmpty(); } })); + + + this._notebookEditors = new Set(); } async batchReplace(elementsToReplace: RenderableMatch[]) { @@ -1170,11 +1214,61 @@ export class SearchResult extends Disposable { return retEvent; } + + private onDidVisibleEditorsChange(): void { + const visibleNotebookEditors = this.editorService.visibleEditorPanes + .map((editor) => ((editor instanceof NotebookEditor) ? (editor as NotebookEditor) : null)) + .filter((editor2): editor2 is NotebookEditor => (NotebookEditor !== null)); + + if (visibleNotebookEditors.length === 0) { + return; + } + + const oldEditors = this._notebookEditors.values(); + + const oldEditorsToUnbind = []; + const oldEditorsToKeepBound = []; + for (const editor of oldEditors) { + if (visibleNotebookEditors.includes(editor)) { + oldEditorsToKeepBound.push(editor); + } else { + oldEditorsToUnbind.push(editor); + } + } + + for (const editor of oldEditorsToUnbind) { + const widget = editor.getControl(); + // doesn't currently work because these fields are usually undefined by now on the editor + if (editor.input && editor.input.resource && widget) { + this.onNotebookEditorRemoved(widget, editor.input.resource); + this._notebookEditors.delete(editor); + } + } + + for (const editor of visibleNotebookEditors) { + const widget = editor.getControl(); + if (editor.input && editor.input.resource && widget) { + this.onNotebookEditorAdded(widget, editor.input.resource); + this._notebookEditors.add(editor); + } + } + } + private onModelAdded(model: ITextModel): void { const folderMatch = this._folderMatchesMap.findSubstr(model.uri); folderMatch?.bindModel(model); } + private onNotebookEditorAdded(editor: NotebookEditorWidget, resource: URI): void { + const folderMatch = this._folderMatchesMap.findSubstr(resource); + folderMatch?.bindEditorWidget(editor, resource); + } + + private onNotebookEditorRemoved(editor: NotebookEditorWidget, resource: URI): void { + const folderMatch = this._folderMatchesMap.findSubstr(resource); + folderMatch?.unbindEditorWidget(editor, resource); + } + private _createBaseFolderMatch(resource: URI | null, id: string, index: number, query: ITextQuery): FolderMatch { let folderMatch; if (resource) { diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index 265e89bc965c0..9f6fac711d6cc 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -24,7 +24,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { FileMatch, Match, RenderableMatch, SearchModel, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, Match, RenderableMatch, SearchModel, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot } from 'vs/workbench/contrib/search/browser/searchModel'; import { isEqual } from 'vs/base/common/resources'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 30454a8e98887..6e74ab2fb3fc7 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -68,10 +68,10 @@ import { renderSearchMessage } from 'vs/workbench/contrib/search/browser/searchM import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/contrib/search/browser/searchResultsView'; import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -537,6 +537,7 @@ export class SearchView extends ViewPane { } refreshTree(event?: IChangeEvent): void { + // animation frame and debounce const collapseResults = this.searchConfig.collapseResults; if (!event || event.added || event.removed) { // Refresh whole tree diff --git a/src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts b/src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts index 681dae8164cc8..31effdd2358f1 100644 --- a/src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts +++ b/src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts @@ -6,7 +6,7 @@ import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; import { Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { RenderableMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; const someEvent = new Emitter().event; diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index 975e022550b71..6a1c09ed5659a 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -16,7 +16,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { IFileMatch } from 'vs/workbench/services/search/common/search'; import { getElementToFocusAfterRemoved, getLastNodeFromSameType } from 'vs/workbench/contrib/search/browser/searchActions'; -import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/browser/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts similarity index 99% rename from src/vs/workbench/contrib/search/test/common/searchModel.test.ts rename to src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index c62b157915d93..ed36923b1dbd5 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -16,7 +16,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchMatch, OneLineRange, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import { SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { FileService } from 'vs/platform/files/common/fileService'; diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts similarity index 99% rename from src/vs/workbench/contrib/search/test/common/searchResult.test.ts rename to src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index b7c72c0c44c03..44a1f266cdc08 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Match, FileMatch, SearchResult, SearchModel, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { Match, FileMatch, SearchResult, SearchModel, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/model'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index a51ea982326dd..402da91997506 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -21,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { FileMatch, FolderMatch, Match, searchComparer, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatch, Match, searchComparer, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; diff --git a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts index 3b73a35ecb3c1..a96a9e886ca54 100644 --- a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts @@ -34,7 +34,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import 'vs/workbench/contrib/search/browser/search.contribution'; // load contributions import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; -import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import { SearchModel } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index a187de8b635f0..b42d54e41e696 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -44,7 +44,7 @@ import { SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; import { InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; -import { SearchModel, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { InSearchEditor, SearchEditorFindMatchClass, SearchEditorID, SearchEditorInputTypeId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import type { SearchConfiguration, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { serializeSearchResultForEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index e1d2e65956d0e..e5cff3fd0fc7b 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -17,7 +17,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { EditorsOrder } from 'vs/workbench/common/editor'; import { IViewsService } from 'vs/workbench/common/views'; import { getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; -import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts index a931eb0a9070c..de40b0c6636b4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorSerialization.ts @@ -10,7 +10,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import type { ITextModel } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; -import { FileMatch, Match, searchMatchComparer, SearchResult, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, Match, searchMatchComparer, SearchResult, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import type { SearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { ITextQuery, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; From cc1a7788fa742e430c0bb4c292ca471bb1de2b7d Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 8 Nov 2022 08:40:04 -0800 Subject: [PATCH 03/35] change notebook track qualifications --- .../notebook/browser/notebookEditorWidget.ts | 4 ++ .../contrib/search/browser/searchModel.ts | 54 ++++++++++--------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 1143d9b628e93..35aa79030ce2b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -146,6 +146,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidResizeOutput = this._onDidResizeOutputEmitter.event; + private readonly _onDidDispose = this._register(new Emitter()); + readonly onDidDispose = this._onDidDispose.event; + //#endregion private _overlayContainer!: HTMLElement; private _notebookTopToolbarContainer!: HTMLElement; @@ -2926,6 +2929,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._baseCellEditorOptions.forEach(v => v.dispose()); this._baseCellEditorOptions.clear(); + this._onDidDispose.fire(); super.dispose(); // unref diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index c8c779bb6d117..f5e0da024cab7 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1098,7 +1098,7 @@ export class SearchResult extends Disposable { private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; - private _notebookEditors: Set; + // private _notebookEditors: Set; private _isDirty = false; constructor( @@ -1123,7 +1123,7 @@ export class SearchResult extends Disposable { })); - this._notebookEditors = new Set(); + // this._notebookEditors = new Set(); } async batchReplace(elementsToReplace: RenderableMatch[]) { @@ -1218,38 +1218,42 @@ export class SearchResult extends Disposable { private onDidVisibleEditorsChange(): void { const visibleNotebookEditors = this.editorService.visibleEditorPanes .map((editor) => ((editor instanceof NotebookEditor) ? (editor as NotebookEditor) : null)) - .filter((editor2): editor2 is NotebookEditor => (NotebookEditor !== null)); + .filter((editor2): editor2 is NotebookEditor => (editor2 !== null)); if (visibleNotebookEditors.length === 0) { return; } - const oldEditors = this._notebookEditors.values(); - - const oldEditorsToUnbind = []; - const oldEditorsToKeepBound = []; - for (const editor of oldEditors) { - if (visibleNotebookEditors.includes(editor)) { - oldEditorsToKeepBound.push(editor); - } else { - oldEditorsToUnbind.push(editor); - } - } - - for (const editor of oldEditorsToUnbind) { - const widget = editor.getControl(); - // doesn't currently work because these fields are usually undefined by now on the editor - if (editor.input && editor.input.resource && widget) { - this.onNotebookEditorRemoved(widget, editor.input.resource); - this._notebookEditors.delete(editor); - } - } + // const oldEditors = this._notebookEditors.values(); + + // const oldEditorsToUnbind = []; + // const oldEditorsToKeepBound = []; + // for (const editor of oldEditors) { + // if (visibleNotebookEditors.includes(editor)) { + // oldEditorsToKeepBound.push(editor); + // } + // // else { + // // oldEditorsToUnbind.push(editor); + // // } + // } + + // for (const editor of oldEditorsToUnbind) { + // const widget = editor.getControl(); + // // doesn't currently work because these fields are usually undefined by now on the editor + // if (editor.input && editor.input.resource && widget) { + // this.onNotebookEditorRemoved(widget, editor.input.resource); + // this._notebookEditors.delete(editor); + // } + // } for (const editor of visibleNotebookEditors) { const widget = editor.getControl(); if (editor.input && editor.input.resource && widget) { - this.onNotebookEditorAdded(widget, editor.input.resource); - this._notebookEditors.add(editor); + const uri = editor.input.resource; + if (uri) { + this.onNotebookEditorAdded(widget, uri); + widget.onDidDispose(() => this.onNotebookEditorRemoved(widget, uri)); + } } } } From 6bba961e550447af7112475989c1b07a43707cc4 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 15 Nov 2022 09:15:19 -0800 Subject: [PATCH 04/35] needs to read into dom --- .../notebook/browser/notebookEditorWidget.ts | 7 +- .../browser/services/notebookEditorService.ts | 4 + .../services/notebookEditorServiceImpl.ts | 15 ++ .../contrib/search/browser/searchModel.ts | 177 +++++++++++------- .../search/browser/searchNotebookHelpers.ts | 108 +++++++++++ .../services/search/common/search.ts | 6 +- 6 files changed, 249 insertions(+), 68 deletions(-) create mode 100644 src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 35aa79030ce2b..7ff88c26cb1d6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -112,6 +112,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD readonly onDidChangeCellState = this._onDidChangeCellState.event; private readonly _onDidChangeViewCells = this._register(new Emitter()); readonly onDidChangeViewCells: Event = this._onDidChangeViewCells.event; + private readonly _onWillChangeModel = this._register(new Emitter()); + readonly onWillChangeModel: Event = this._onWillChangeModel.event; private readonly _onDidChangeModel = this._register(new Emitter()); readonly onDidChangeModel: Event = this._onDidChangeModel.event; private readonly _onDidChangeOptions = this._register(new Emitter()); @@ -144,8 +146,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private readonly onDidRenderOutput = this._onDidRenderOutput.event; private readonly _onDidResizeOutputEmitter = this._register(new Emitter()); readonly onDidResizeOutput = this._onDidResizeOutputEmitter.event; - - private readonly _onDidDispose = this._register(new Emitter()); readonly onDidDispose = this._onDidDispose.event; @@ -196,6 +196,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } set viewModel(newModel: NotebookViewModel | undefined) { + this._onWillChangeModel.fire(this._notebookViewModel?.notebookDocument); this._notebookViewModel = newModel; this._onDidChangeModel.fire(newModel?.notebookDocument); } @@ -2387,7 +2388,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } async find(query: string, options: INotebookSearchOptions, token: CancellationToken, skipWarmup: boolean = false): Promise { - // here if (!this._notebookViewModel) { return []; } @@ -2924,6 +2924,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._overlayContainer.remove(); this.viewModel?.dispose(); + this.viewModel = undefined; this._renderedEditors.clear(); this._baseCellEditorOptions.forEach(v => v.dispose()); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index ac80184de3e8b..2e7b1453f42b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -9,6 +9,8 @@ import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebo import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Event } from 'vs/base/common/event'; import { Dimension } from 'vs/base/browser/dom'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { URI } from 'vs/base/common/uri'; export const INotebookEditorService = createDecorator('INotebookEditorWidgetService'); @@ -20,9 +22,11 @@ export interface INotebookEditorService { _serviceBrand: undefined; retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension): IBorrowValue; + retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; onDidAddNotebookEditor: Event; onDidRemoveNotebookEditor: Event; + onDidAddNotebookEditorWidget: Event; addNotebookEditor(editor: INotebookEditor): void; removeNotebookEditor(editor: INotebookEditor): void; getNotebookEditor(editorId: string): INotebookEditor | undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index d83e19338cb01..c9069cf5a8100 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -14,6 +14,7 @@ import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/co import { Emitter } from 'vs/base/common/event'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { Dimension } from 'vs/base/browser/dom'; +import { URI } from 'vs/base/common/uri'; export class NotebookEditorWidgetService implements INotebookEditorService { @@ -29,6 +30,9 @@ export class NotebookEditorWidgetService implements INotebookEditorService { readonly onDidAddNotebookEditor = this._onNotebookEditorAdd.event; readonly onDidRemoveNotebookEditor = this._onNotebookEditorsRemove.event; + private readonly _onDidAddNotebookEditorWidget = new Emitter(); + readonly onDidAddNotebookEditorWidget = this._onDidAddNotebookEditorWidget.event; + private readonly _borrowableEditors = new Map>(); constructor( @@ -129,6 +133,16 @@ export class NotebookEditorWidgetService implements INotebookEditorService { targetMap.set(input.resource, widget); } + retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined { + for (const widgetInfo of this._borrowableEditors.values()) { + const widget = widgetInfo.get(resource); + if (widget) { + return this._createBorrowValue(widget.token!, widget); + } + } + return undefined; + } + retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension): IBorrowValue { let value = this._borrowableEditors.get(group.id)?.get(input.resource); @@ -137,6 +151,7 @@ export class NotebookEditorWidgetService implements INotebookEditorService { // NEW widget const instantiationService = accessor.get(IInstantiationService); const widget = instantiationService.createInstance(NotebookEditorWidget, creationOptions ?? getDefaultNotebookCreationOptions(), initialDimension); + this._onDidAddNotebookEditorWidget.fire(widget); const token = this._tokenPool++; value = { widget, token }; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index f5e0da024cab7..16c66e2d35233 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { RunOnceScheduler } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { compareFileExtensions, compareFileNames, comparePaths } from 'vs/base/common/comparers'; import { memoize } from 'vs/base/common/decorators'; import * as errors from 'vs/base/common/errors'; @@ -30,10 +30,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -46,11 +45,10 @@ export class Match { private _range: Range; private _oneLinePreviewText: string; private _rangeInPreviewText: ISearchRange; - // For replace private _fullPreviewRange: ISearchRange; - constructor(private _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange) { + constructor(private _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, private _cellFragment?: string) { this._oneLinePreviewText = _fullPreviewLines[_fullPreviewRange.startLineNumber]; const adjustedEndCol = _fullPreviewRange.startLineNumber === _fullPreviewRange.endLineNumber ? _fullPreviewRange.endColumn : @@ -84,6 +82,10 @@ export class Match { return this._range; } + fragment(): string | undefined { + return this._cellFragment; + } + @memoize preview(): { before: string; inside: string; after: string } { let before = this._oneLinePreviewText.substring(0, this._rangeInPreviewText.startColumn - 1), @@ -211,6 +213,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _model: ITextModel | null = null; private _notebookEditorWidget: NotebookEditorWidget | null = null; private _modelListener: IDisposable | null = null; + private _editorWidgetListener: IDisposable | null = null; private _matches: Map; private _removedMatches: Set; private _selectedMatch: Match | null = null; @@ -234,6 +237,7 @@ export class FileMatch extends Disposable implements IFileMatch { @IModelService private readonly modelService: IModelService, @IReplaceService private readonly replaceService: IReplaceService, @ILabelService readonly labelService: ILabelService, + @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); this._resource = this.rawMatch.resource; @@ -248,9 +252,13 @@ export class FileMatch extends Disposable implements IFileMatch { return this._closestRoot; } - private createMatches(): void { + private async createMatches(): Promise { const model = this.modelService.getModel(this._resource); - if (model) { + const notebookEditorWidgetBorrow = this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource); + if (notebookEditorWidgetBorrow?.value) { + this.bindEditorWidget(notebookEditorWidgetBorrow.value); + await this.updateMatchesForEditorWidget(); + } else if (model) { this.bindModel(model); this.updateMatchesForModel(); } else { @@ -276,10 +284,20 @@ export class FileMatch extends Disposable implements IFileMatch { bindEditorWidget(widget: NotebookEditorWidget) { this._notebookEditorWidget = widget; + this._editorWidgetListener = this._notebookEditorWidget.viewModel?.onDidChangeViewCells(() => { + this._updateScheduler.schedule(); + }) ?? null; + this.updateHighlights(); console.log(`added widget ${this._notebookEditorWidget.textModel?.uri}`); } unbindEditorWidget(widget: NotebookEditorWidget) { + this.updateMatchesForModel(); + if (this._notebookEditorWidget) { + this._updateScheduler.cancel(); + this._model = null; + this._editorWidgetListener!.dispose(); + } this._notebookEditorWidget = null; console.log(`removed widget ${widget.textModel?.uri}`); } @@ -313,14 +331,48 @@ export class FileMatch extends Disposable implements IFileMatch { const matches = this._model .findMatches(this._query.pattern, this._model.getFullModelRange(), !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); - this.updateMatches(matches, true); + this.updateMatches(matches, true, this._model); + } + + private async updateMatchesForEditorWidget(): Promise { + // this is called from a timeout and might fire + // after the model has been disposed + if (!this._notebookEditorWidget) { + return; + } + this._matches = new Map(); + + const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; + const allMatches = await this._notebookEditorWidget + .find(this._query.pattern, { + regex: this._query.isRegExp, + wholeWord: this._query.isWordMatch, + caseSensitive: this._query.isCaseSensitive, + wordSeparators: wordSeparators ?? undefined, + includeMarkupInput: true, + includeCodeInput: true + }, CancellationToken.None); + // const matches = allMatches.map((elem) => elem.matches) + // .flat() + // .map((elem) => { + // if (elem instanceof FindMatch) { + // return elem; + // } else { + // return undefined; + // } + // }).filter((e): e is FindMatch => !!FindMatch); + console.log(allMatches); + const model = this._notebookEditorWidget.textModel; + if (!model) { + return; + } + // this.updateNotebookMatches(allMatches, true); } private updatesMatchesForLineAfterReplace(lineNumber: number, modelChange: boolean): void { if (!this._model) { return; } - const range = { startLineNumber: lineNumber, startColumn: this._model.getLineMinColumn(lineNumber), @@ -332,15 +384,38 @@ export class FileMatch extends Disposable implements IFileMatch { const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); - this.updateMatches(matches, modelChange); - } - - private updateMatches(matches: FindMatch[], modelChange: boolean): void { - if (!this._model) { - return; - } - - const textSearchResults = editorMatchesToTextSearchResults(matches, this._model, this._previewOptions); + this.updateMatches(matches, modelChange, this._model); + } + + // private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { + // if (!this._notebookEditorWidget) { + // return; + // } + + // const textSearchResults = matches.forEach((m) => notebookEditorMatchesToTextSearchResults(m.matches, this._notebookEditorWidget, this._previewOptions)); + // textSearchResults.forEach(textSearchResult => { + // textSearchResultToMatches(textSearchResult, this).forEach(match => { + // if (!this._removedMatches.has(match.id())) { + // this.add(match); + // if (this.isMatchSelected(match)) { + // this._selectedMatch = match; + // } + // } + // }); + // }); + + // this.addContext( + // addContextToNotebookEditorMatches(textSearchResults, this._notebookEditorWidget, this.parent().parent().query!) + // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); + + // this._onChange.fire({ forceUpdateModel: modelChange }); + // this.updateHighlights(); + // } + + private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { + + const textSearchResults = editorMatchesToTextSearchResults(matches, model, this._previewOptions); textSearchResults.forEach(textSearchResult => { textSearchResultToMatches(textSearchResult, this).forEach(match => { if (!this._removedMatches.has(match.id())) { @@ -353,7 +428,7 @@ export class FileMatch extends Disposable implements IFileMatch { }); this.addContext( - addContextToEditorMatches(textSearchResults, this._model, this.parent().parent().query!) + addContextToEditorMatches(textSearchResults, model, this.parent().parent().query!) .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); @@ -1098,7 +1173,6 @@ export class SearchResult extends Disposable { private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; - // private _notebookEditors: Set; private _isDirty = false; constructor( @@ -1107,15 +1181,14 @@ export class SearchResult extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @IEditorService private readonly editorService: IEditorService, + @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); - this._register(this.editorService.onDidVisibleEditorsChange(() => { - this.onDidVisibleEditorsChange(); - })); + this._register(this.notebookEditorService.onDidAddNotebookEditorWidget(widget => this.onDidAddNotebookEditorWidget(widget))); + this._register(this.onChange(e => { if (e.removed) { this._isDirty = !this.isEmpty(); @@ -1215,47 +1288,23 @@ export class SearchResult extends Disposable { return retEvent; } - private onDidVisibleEditorsChange(): void { - const visibleNotebookEditors = this.editorService.visibleEditorPanes - .map((editor) => ((editor instanceof NotebookEditor) ? (editor as NotebookEditor) : null)) - .filter((editor2): editor2 is NotebookEditor => (editor2 !== null)); - - if (visibleNotebookEditors.length === 0) { - return; - } + private onDidAddNotebookEditorWidget(widget: NotebookEditorWidget): void { + widget.onWillChangeModel( + (model) => { + if (model) { + this.onNotebookEditorWidgetRemoved(widget, model?.uri); + } + } + ); - // const oldEditors = this._notebookEditors.values(); - - // const oldEditorsToUnbind = []; - // const oldEditorsToKeepBound = []; - // for (const editor of oldEditors) { - // if (visibleNotebookEditors.includes(editor)) { - // oldEditorsToKeepBound.push(editor); - // } - // // else { - // // oldEditorsToUnbind.push(editor); - // // } - // } - - // for (const editor of oldEditorsToUnbind) { - // const widget = editor.getControl(); - // // doesn't currently work because these fields are usually undefined by now on the editor - // if (editor.input && editor.input.resource && widget) { - // this.onNotebookEditorRemoved(widget, editor.input.resource); - // this._notebookEditors.delete(editor); - // } - // } - - for (const editor of visibleNotebookEditors) { - const widget = editor.getControl(); - if (editor.input && editor.input.resource && widget) { - const uri = editor.input.resource; - if (uri) { - this.onNotebookEditorAdded(widget, uri); - widget.onDidDispose(() => this.onNotebookEditorRemoved(widget, uri)); + widget.onDidChangeModel( + (model) => { + if (model) { + this.onNotebookEditorWidgetAdded(widget, model?.uri); } } - } + ); + } private onModelAdded(model: ITextModel): void { @@ -1263,12 +1312,12 @@ export class SearchResult extends Disposable { folderMatch?.bindModel(model); } - private onNotebookEditorAdded(editor: NotebookEditorWidget, resource: URI): void { + private onNotebookEditorWidgetAdded(editor: NotebookEditorWidget, resource: URI): void { const folderMatch = this._folderMatchesMap.findSubstr(resource); folderMatch?.bindEditorWidget(editor, resource); } - private onNotebookEditorRemoved(editor: NotebookEditorWidget, resource: URI): void { + private onNotebookEditorWidgetRemoved(editor: NotebookEditorWidget, resource: URI): void { const folderMatch = this._folderMatchesMap.findSubstr(resource); folderMatch?.unbindEditorWidget(editor, resource); } diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts new file mode 100644 index 0000000000000..4f6c54146ef70 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// import { FindMatch } from 'vs/editor/common/model'; +// import { CellFindMatchWithIndex, ICellViewModel, OutputFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +// import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +// import { ITextSearchPreviewOptions, ITextSearchResult, TextSearchMatch } from 'vs/workbench/services/search/common/search'; + +// interface CellFindMatchInfoForTextModel { +// cell: ICellViewModel; +// matches: FindMatch[] | OutputFindMatch; +// } + +// function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch { +// // what do I want here? +// // I want to take all of my matches and group them +// const matches: FindMatch[] | OutputFindMatch = cellInfo.matches; +// if (matches instanceof OutputFindMatch) { + +// return new TextSearchMatch( +// lineTexts.join('\n') + '\n', +// matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), +// previewOptions); +// } else { +// const firstLine = matches[0].range.startLineNumber; +// const lastLine = matches[matches.length - 1].range.endLineNumber; +// const lineTexts: string[] = []; +// for (let i = firstLine; i <= lastLine; i++) { +// lineTexts.push(editorWidget.textModel?.getLineContent(i)); +// } + +// return new TextSearchMatch( +// lineTexts.join('\n') + '\n', +// matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), +// previewOptions); +// } + +// } + +// export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { +// let previousEndLine = -1; +// const groupedMatches: CellFindMatchInfoForTextModel[] = []; +// let currentMatches: FindMatch[] = []; + + +// cellFindMatches.forEach((cellFindMatch) => { +// cellFindMatch.matches.forEach((match) => { +// if (match instanceof FindMatch) { + +// if (match.range.startLineNumber !== previousEndLine) { +// currentMatches = []; +// groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); +// } + +// currentMatches.push(match); +// previousEndLine = match.range.endLineNumber; +// } else { +// groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); +// } +// }); + +// currentMatches = []; +// groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); +// }); + +// return groupedMatches.map(sameLineMatches => { + +// return notebookEditorMatchToTextSearchResult(sameLineMatches, editorWidget, previewOptions); +// }); +// } + +// export function addContextToNotebookEditorMatches(matches: ITextSearchMatch[], editorWidget: NotebookEditorWidget, query: ITextQuery): ITextSearchResult[] { +// const results: ITextSearchResult[] = []; + +// let prevLine = -1; +// for (let i = 0; i < matches.length; i++) { +// const { start: matchStartLine, end: matchEndLine } = getMatchStartEnd(matches[i]); +// if (typeof query.beforeContext === 'number' && query.beforeContext > 0) { +// const beforeContextStartLine = Math.max(prevLine + 1, matchStartLine - query.beforeContext); +// for (let b = beforeContextStartLine; b < matchStartLine; b++) { +// results.push({ +// text: model.getLineContent(b + 1), +// lineNumber: b +// }); +// } +// } + +// results.push(matches[i]); + +// const nextMatch = matches[i + 1]; +// const nextMatchStartLine = nextMatch ? getMatchStartEnd(nextMatch).start : Number.MAX_VALUE; +// if (typeof query.afterContext === 'number' && query.afterContext > 0) { +// const afterContextToLine = Math.min(nextMatchStartLine - 1, matchEndLine + query.afterContext, model.getLineCount() - 1); +// for (let a = matchEndLine + 1; a <= afterContextToLine; a++) { +// results.push({ +// text: model.getLineContent(a + 1), +// lineNumber: a +// }); +// } +// } + +// prevLine = matchEndLine; +// } + +// return results; +// } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 2ed83f3be21e3..1d0cdb843fc1c 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -174,12 +174,14 @@ export interface ISearchRange { export interface ITextSearchResultPreview { text: string; matches: ISearchRange | ISearchRange[]; + cellFragment?: string; } export interface ITextSearchMatch { uri?: URI; ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; + cellFragment?: string; } export interface ITextSearchContext { @@ -273,9 +275,11 @@ export class FileMatch implements IFileMatch { export class TextSearchMatch implements ITextSearchMatch { ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; + cellFragment?: string; - constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { + constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions, cellFragment?: string) { this.ranges = range; + this.cellFragment = cellFragment; // Trim preview if this is one match and a single-line match with a preview requested. // Otherwise send the full text, like for replace or for showing multiple previews. From 75dddd2f4065fb2236cc4460fb3bb8665e575dbf Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 17 Nov 2022 09:54:02 -0800 Subject: [PATCH 05/35] random logs --- .../browser/view/renderers/webviewPreloads.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index fe811a05ac961..bf68ccb546700 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -983,6 +983,24 @@ async function webviewPreloads(ctx: PreloadContext) { } } + function extractSelectionLine(selection: Selection) { + + const range = selection.getRangeAt(0); + const oldRange = document.createRange(); + oldRange.setStart(range.startContainer, range.startOffset); + oldRange.setEnd(range.endContainer, range.endOffset); + + selection.modify('move', 'backward', 'lineboundary'); + selection.modify('extend', 'forward', 'lineboundary'); + + const line = selection.toString(); + + // re-add the old range so that the selection is restored + selection.removeAllRanges(); + selection.addRange(oldRange); + + return line; + } const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean }) => { let find = true; const matches: IFindMatch[] = []; @@ -1020,6 +1038,7 @@ async function webviewPreloads(ctx: PreloadContext) { const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; if (shadowSelection && shadowSelection.anchorNode) { + console.log(extractSelectionLine(shadowSelection)); matches.push({ type: 'preview', id: preview.id, @@ -1039,6 +1058,7 @@ async function webviewPreloads(ctx: PreloadContext) { const root = outputNode.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; if (shadowSelection && shadowSelection.anchorNode) { + console.log(extractSelectionLine(shadowSelection)); matches.push({ type: 'output', id: outputNode.id, @@ -1056,6 +1076,8 @@ async function webviewPreloads(ctx: PreloadContext) { const lastEl: any = matches.length ? matches[matches.length - 1] : null; if (lastEl && lastEl.container.contains(anchorNode) && options.includeOutput) { + console.log('here1'); + console.log(lastEl); matches.push({ type: lastEl.type, id: lastEl.id, @@ -1075,6 +1097,8 @@ async function webviewPreloads(ctx: PreloadContext) { // inside output const cellId = node.parentElement?.parentElement?.id; if (cellId) { + console.log('here2'); + console.log(node); matches.push({ type: 'output', id: node.id, From b7f40ea4ed09e4ab40b8392398de8706d4209427 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 22 Nov 2022 10:25:10 -0800 Subject: [PATCH 06/35] some progress on notebook search --- .../notebook/browser/notebookBrowser.ts | 9 ++ .../browser/view/renderers/webviewMessages.ts | 9 ++ .../browser/view/renderers/webviewPreloads.ts | 29 +++- .../contrib/search/browser/searchModel.ts | 60 ++++---- .../search/browser/searchNotebookHelpers.ts | 144 ++++++++++-------- 5 files changed, 151 insertions(+), 100 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 95df7696197bb..f7e0cf11af0de 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -692,8 +692,17 @@ export interface IActiveNotebookEditorDelegate extends INotebookEditorDelegate { getNextVisibleCellIndex(index: number): number; } +export interface ISearchPreviewInfo { + line: string; + range: { + start: number; + end: number; + }; +} + export interface OutputFindMatch { readonly index: number; + readonly searchPreviewInfo?: ISearchPreviewInfo; } export interface CellFindMatch { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index be3698df4bf76..2d70a2d120d3a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -411,11 +411,20 @@ export interface IFindStopMessage { readonly type: 'findStop'; } +export interface ISearchPreviewInfo { + line: string; + range: { + start: number; + end: number; + }; +} + export interface IFindMatch { readonly type: 'preview' | 'output'; readonly cellId: string; readonly id: string; readonly index: number; + readonly searchPreviewInfo?: ISearchPreviewInfo; } export interface IDidFindMessage extends BaseToWebviewMessage { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index bf68ccb546700..b7156534b65a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -857,6 +857,15 @@ async function webviewPreloads(ctx: PreloadContext) { originalRange: Range; isShadow: boolean; highlightResult?: IHighlightResult; + searchPreviewInfo?: ISearchPreviewInfo; + } + + interface ISearchPreviewInfo { + line: string; + range: { + start: number; + end: number; + }; } interface IHighlighter { @@ -983,7 +992,7 @@ async function webviewPreloads(ctx: PreloadContext) { } } - function extractSelectionLine(selection: Selection) { + function extractSelectionLine(selection: Selection): ISearchPreviewInfo { const range = selection.getRangeAt(0); const oldRange = document.createRange(); @@ -993,14 +1002,20 @@ async function webviewPreloads(ctx: PreloadContext) { selection.modify('move', 'backward', 'lineboundary'); selection.modify('extend', 'forward', 'lineboundary'); + const newRange = selection.getRangeAt(0); const line = selection.toString(); + const lineRange = { + start: oldRange.startOffset - newRange.startOffset, + end: oldRange.endOffset - newRange.startOffset, + }; // re-add the old range so that the selection is restored selection.removeAllRanges(); selection.addRange(oldRange); - return line; + return { line, range: lineRange }; } + const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean }) => { let find = true; const matches: IFindMatch[] = []; @@ -1038,14 +1053,14 @@ async function webviewPreloads(ctx: PreloadContext) { const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; if (shadowSelection && shadowSelection.anchorNode) { - console.log(extractSelectionLine(shadowSelection)); matches.push({ type: 'preview', id: preview.id, cellId: preview.id, container: preview, isShadow: true, - originalRange: shadowSelection.getRangeAt(0) + originalRange: shadowSelection.getRangeAt(0), + searchPreviewInfo: extractSelectionLine(shadowSelection), }); } } @@ -1065,7 +1080,8 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, container: outputNode, isShadow: true, - originalRange: shadowSelection.getRangeAt(0) + originalRange: shadowSelection.getRangeAt(0), + searchPreviewInfo: extractSelectionLine(shadowSelection) }); } } @@ -1141,7 +1157,8 @@ async function webviewPreloads(ctx: PreloadContext) { type: match.type, id: match.id, cellId: match.cellId, - index + index, + searchPreviewInfo: match.searchPreviewInfo, })) }); }; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 16c66e2d35233..41c129da17bc2 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -30,9 +30,13 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +// import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; +import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +// import { addContextToNotebookEditorMatches, notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -350,7 +354,9 @@ export class FileMatch extends Disposable implements IFileMatch { caseSensitive: this._query.isCaseSensitive, wordSeparators: wordSeparators ?? undefined, includeMarkupInput: true, - includeCodeInput: true + includeMarkupPreview: true, + includeCodeInput: true, + includeOutput: true, }, CancellationToken.None); // const matches = allMatches.map((elem) => elem.matches) // .flat() @@ -366,7 +372,7 @@ export class FileMatch extends Disposable implements IFileMatch { if (!model) { return; } - // this.updateNotebookMatches(allMatches, true); + this.updateNotebookMatches(allMatches, true); } private updatesMatchesForLineAfterReplace(lineNumber: number, modelChange: boolean): void { @@ -387,31 +393,31 @@ export class FileMatch extends Disposable implements IFileMatch { this.updateMatches(matches, modelChange, this._model); } - // private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { - // if (!this._notebookEditorWidget) { - // return; - // } - - // const textSearchResults = matches.forEach((m) => notebookEditorMatchesToTextSearchResults(m.matches, this._notebookEditorWidget, this._previewOptions)); - // textSearchResults.forEach(textSearchResult => { - // textSearchResultToMatches(textSearchResult, this).forEach(match => { - // if (!this._removedMatches.has(match.id())) { - // this.add(match); - // if (this.isMatchSelected(match)) { - // this._selectedMatch = match; - // } - // } - // }); - // }); - - // this.addContext( - // addContextToNotebookEditorMatches(textSearchResults, this._notebookEditorWidget, this.parent().parent().query!) - // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) - // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); - - // this._onChange.fire({ forceUpdateModel: modelChange }); - // this.updateHighlights(); - // } + private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { + if (!this._notebookEditorWidget) { + return; + } + + const textSearchResults = notebookEditorMatchesToTextSearchResults(matches, this._notebookEditorWidget, this._previewOptions); + textSearchResults.forEach(textSearchResult => { + textSearchResultToMatches(textSearchResult, this).forEach(match => { + if (!this._removedMatches.has(match.id())) { + this.add(match); + if (this.isMatchSelected(match)) { + this._selectedMatch = match; + } + } + }); + }); + + // this.addContext( + // addContextToNotebookEditorMatches(textSearchResults, this._notebookEditorWidget, this.parent().parent().query!) + // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); + + this._onChange.fire({ forceUpdateModel: modelChange }); + // this.updateHighlights(); + } private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index 4f6c54146ef70..6efd5fd56ce24 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -3,73 +3,83 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import { FindMatch } from 'vs/editor/common/model'; -// import { CellFindMatchWithIndex, ICellViewModel, OutputFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -// import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; -// import { ITextSearchPreviewOptions, ITextSearchResult, TextSearchMatch } from 'vs/workbench/services/search/common/search'; - -// interface CellFindMatchInfoForTextModel { -// cell: ICellViewModel; -// matches: FindMatch[] | OutputFindMatch; -// } - -// function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch { -// // what do I want here? -// // I want to take all of my matches and group them -// const matches: FindMatch[] | OutputFindMatch = cellInfo.matches; -// if (matches instanceof OutputFindMatch) { - -// return new TextSearchMatch( -// lineTexts.join('\n') + '\n', -// matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), -// previewOptions); -// } else { -// const firstLine = matches[0].range.startLineNumber; -// const lastLine = matches[matches.length - 1].range.endLineNumber; -// const lineTexts: string[] = []; -// for (let i = firstLine; i <= lastLine; i++) { -// lineTexts.push(editorWidget.textModel?.getLineContent(i)); -// } - -// return new TextSearchMatch( -// lineTexts.join('\n') + '\n', -// matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), -// previewOptions); -// } - -// } - -// export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { -// let previousEndLine = -1; -// const groupedMatches: CellFindMatchInfoForTextModel[] = []; -// let currentMatches: FindMatch[] = []; - - -// cellFindMatches.forEach((cellFindMatch) => { -// cellFindMatch.matches.forEach((match) => { -// if (match instanceof FindMatch) { - -// if (match.range.startLineNumber !== previousEndLine) { -// currentMatches = []; -// groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); -// } - -// currentMatches.push(match); -// previousEndLine = match.range.endLineNumber; -// } else { -// groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); -// } -// }); - -// currentMatches = []; -// groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); -// }); - -// return groupedMatches.map(sameLineMatches => { - -// return notebookEditorMatchToTextSearchResult(sameLineMatches, editorWidget, previewOptions); -// }); -// } +import { FindMatch } from 'vs/editor/common/model'; +import { CellFindMatchWithIndex, ICellViewModel, OutputFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { ITextSearchPreviewOptions, SearchRange, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { Range } from 'vs/editor/common/core/range'; + +interface CellFindMatchInfoForTextModel { + cell: ICellViewModel; + matches: FindMatch[] | OutputFindMatch; +} + +function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch | undefined { + const matches = cellInfo.matches; + + + if (Array.isArray(matches)) { + + const firstLine = matches[0].range.startLineNumber; + const lastLine = matches[matches.length - 1].range.endLineNumber; + const lineTexts: string[] = []; + for (let i = firstLine; i <= lastLine; i++) { + if (cellInfo.cell.textModel) { + lineTexts.push(cellInfo.cell.textModel?.getLineContent(i)); + } + } + + return new TextSearchMatch( + lineTexts.join('\n') + '\n', + matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), + previewOptions); + + } + else if (matches.searchPreviewInfo) { + return new TextSearchMatch( + matches.searchPreviewInfo.line, + new Range(0, matches.searchPreviewInfo.range.start, 1, matches.searchPreviewInfo.range.end), + previewOptions); + } + + + const ranges = new SearchRange(0, 0, 1, 1); + return new TextSearchMatch( + '\n', + ranges, + previewOptions); +} +export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { + let previousEndLine = -1; + const groupedMatches: CellFindMatchInfoForTextModel[] = []; + let currentMatches: FindMatch[] = []; + + + cellFindMatches.forEach((cellFindMatch) => { + cellFindMatch.matches.forEach((match) => { + if (match instanceof FindMatch) { + + if (match.range.startLineNumber !== previousEndLine) { + currentMatches = []; + groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); + } + + currentMatches.push(match); + previousEndLine = match.range.endLineNumber; + } else { + groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); + } + }); + + currentMatches = []; + groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); + }); + + return groupedMatches.map(sameLineMatches => { + + return notebookEditorMatchToTextSearchResult(sameLineMatches, editorWidget, previewOptions); + }).filter((item): item is TextSearchMatch => !!item); +} // export function addContextToNotebookEditorMatches(matches: ITextSearchMatch[], editorWidget: NotebookEditorWidget, query: ITextQuery): ITextSearchResult[] { // const results: ITextSearchResult[] = []; From 90763cc033af22bcc9678b351f62ddb9fd6a9b52 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 23 Nov 2022 14:15:35 -0800 Subject: [PATCH 07/35] some rendered content showing in search viewlet --- .../notebook/browser/notebookBrowser.ts | 2 +- .../browser/view/renderers/webviewMessages.ts | 2 +- .../browser/view/renderers/webviewPreloads.ts | 8 +- .../test/browser/contrib/find.test.ts | 18 ++++- .../search/browser/searchActionsBase.ts | 2 +- .../search/browser/searchActionsCopy.ts | 2 +- .../search/browser/searchActionsFind.ts | 2 +- .../search/browser/searchActionsNav.ts | 2 +- .../browser/searchActionsRemoveReplace.ts | 4 +- .../search/browser/searchActionsTopBar.ts | 2 +- .../contrib/search/browser/searchModel.ts | 33 ++------ .../search/browser/searchNotebookHelpers.ts | 78 +++++++++---------- 12 files changed, 73 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 892f3dc94a0a1..44d318c17086d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -711,7 +711,7 @@ export interface ISearchPreviewInfo { export interface CellWebviewFindMatch { readonly index: number; - readonly searchPreviewInfo?: ISearchPreviewInfo; + readonly searchPreviewInfo: ISearchPreviewInfo; } export type CellContentFindMatch = FindMatch; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 3e46646d23e05..9f01237fa5378 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -424,7 +424,7 @@ export interface IFindMatch { readonly cellId: string; readonly id: string; readonly index: number; - readonly searchPreviewInfo?: ISearchPreviewInfo; + readonly searchPreviewInfo: ISearchPreviewInfo; } export interface IDidFindMessage extends BaseToWebviewMessage { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 10348dc85ff69..0315144e1dc32 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -862,8 +862,8 @@ async function webviewPreloads(ctx: PreloadContext) { container: Node; originalRange: Range; isShadow: boolean; + searchPreviewInfo: ISearchPreviewInfo; highlightResult?: IHighlightResult; - searchPreviewInfo?: ISearchPreviewInfo; } interface ISearchPreviewInfo { @@ -1110,7 +1110,8 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: lastEl.cellId, container: lastEl.container, isShadow: false, - originalRange: selection.getRangeAt(0) + originalRange: selection.getRangeAt(0), + searchPreviewInfo: extractSelectionLine(selection), }); } else { @@ -1132,7 +1133,8 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, container: node, isShadow: false, - originalRange: selection.getRangeAt(0) + originalRange: selection.getRangeAt(0), + searchPreviewInfo: extractSelectionLine(selection), }); } break; diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts index 60d80fcd37d36..b941e70675e6f 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts @@ -277,9 +277,23 @@ suite('Notebook Find', () => { mdModel.contentMatches.push(new FindMatch(new Range(1, 1, 1, 2), [])); assert.strictEqual(mdModel.length, 1); mdModel.webviewMatches.push({ - index: 0 + index: 0, + searchPreviewInfo: { + line: '', + range: { + start: 0, + end: 0, + } + } }, { - index: 1 + index: 1, + searchPreviewInfo: { + line: '', + range: { + start: 0, + end: 0, + } + } }); assert.strictEqual(mdModel.length, 3); diff --git a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts index 932b714e3cecc..8c74c35c72e6c 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsBase.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsBase.ts @@ -11,7 +11,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; import { IViewsService } from 'vs/workbench/common/views'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { RenderableMatch, searchComparer } from 'vs/workbench/contrib/search/common/searchModel'; +import { RenderableMatch, searchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; export const category = { value: nls.localize('search', "Search"), original: 'Search' }; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts index 58db3b759d526..7191dc4b41ba8 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsCopy.ts @@ -8,7 +8,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { ILabelService } from 'vs/platform/label/common/label'; import { IViewsService } from 'vs/workbench/common/views'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer } from 'vs/workbench/contrib/search/browser/searchModel'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts index 2d8cf8d99294b..c76be757abde7 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsFind.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsFind.ts @@ -11,7 +11,7 @@ import { IListService, WorkbenchCompressibleObjectTree } from 'vs/platform/list/ import { IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { FileMatch, FolderMatchWithResource, Match, RenderableMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatchWithResource, Match, RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { OpenSearchEditorArgs } from 'vs/workbench/contrib/searchEditor/browser/searchEditor.contribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts index 77a6b79b1f1da..e49950c8eb239 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsNav.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsNav.ts @@ -12,7 +12,7 @@ import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listSe import { IViewsService } from 'vs/workbench/common/views'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; -import { FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index 857f868b810c9..e318b33266a64 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -12,8 +12,8 @@ import { IViewsService } from 'vs/workbench/common/views'; import { searchRemoveIcon, searchReplaceIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; -import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; +import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts index 9c9d8c92057f9..857c4f712665e 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts @@ -11,7 +11,7 @@ import { IViewsService } from 'vs/workbench/common/views'; import { searchClearIcon, searchCollapseAllIcon, searchExpandAllIcon, searchRefreshIcon, searchShowAsList, searchShowAsTree, searchStopIcon } from 'vs/workbench/contrib/search/browser/searchIcons'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, Match, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FolderMatch, FolderMatchNoRoot, FolderMatchWorkspaceRoot, Match, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { VIEW_ID } from 'vs/workbench/services/search/common/search'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index e5e92b1cd28ba..98a34d2ea8c4d 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -51,7 +51,7 @@ export class Match { // For replace private _fullPreviewRange: ISearchRange; - constructor(private _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, private _cellFragment?: string) { + constructor(private _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange) { this._oneLinePreviewText = _fullPreviewLines[_fullPreviewRange.startLineNumber]; const adjustedEndCol = _fullPreviewRange.startLineNumber === _fullPreviewRange.endLineNumber ? _fullPreviewRange.endColumn : @@ -85,10 +85,6 @@ export class Match { return this._range; } - fragment(): string | undefined { - return this._cellFragment; - } - @memoize preview(): { before: string; inside: string; after: string } { let before = this._oneLinePreviewText.substring(0, this._rangeInPreviewText.startColumn - 1), @@ -352,25 +348,12 @@ export class FileMatch extends Disposable implements IFileMatch { wholeWord: this._query.isWordMatch, caseSensitive: this._query.isCaseSensitive, wordSeparators: wordSeparators ?? undefined, - includeMarkupInput: true, + includeMarkupInput: false, includeMarkupPreview: true, includeCodeInput: true, includeOutput: true, }, CancellationToken.None); - // const matches = allMatches.map((elem) => elem.matches) - // .flat() - // .map((elem) => { - // if (elem instanceof FindMatch) { - // return elem; - // } else { - // return undefined; - // } - // }).filter((e): e is FindMatch => !!FindMatch); - console.log(allMatches); - const model = this._notebookEditorWidget.textModel; - if (!model) { - return; - } + this.updateNotebookMatches(allMatches, true); } @@ -397,7 +380,7 @@ export class FileMatch extends Disposable implements IFileMatch { return; } - const textSearchResults = notebookEditorMatchesToTextSearchResults(matches, this._notebookEditorWidget, this._previewOptions); + const textSearchResults = notebookEditorMatchesToTextSearchResults(matches, this._previewOptions); textSearchResults.forEach(textSearchResult => { textSearchResultToMatches(textSearchResult, this).forEach(match => { if (!this._removedMatches.has(match.id())) { @@ -409,10 +392,10 @@ export class FileMatch extends Disposable implements IFileMatch { }); }); - // this.addContext( - // addContextToNotebookEditorMatches(textSearchResults, this._notebookEditorWidget, this.parent().parent().query!) - // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) - // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); + // this.addContext( + // addContextToEditorMatches(textSearchResults, model, this.parent().parent().query!) + // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); this._onChange.fire({ forceUpdateModel: modelChange }); // this.updateHighlights(); diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index 6efd5fd56ce24..24823acfdae48 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -4,81 +4,73 @@ *--------------------------------------------------------------------------------------------*/ import { FindMatch } from 'vs/editor/common/model'; -import { CellFindMatchWithIndex, ICellViewModel, OutputFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; -import { ITextSearchPreviewOptions, SearchRange, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; + +import { ITextSearchPreviewOptions, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { Range } from 'vs/editor/common/core/range'; interface CellFindMatchInfoForTextModel { cell: ICellViewModel; - matches: FindMatch[] | OutputFindMatch; + matches: FindMatch[] | CellWebviewFindMatch; } -function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch | undefined { +function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch | undefined { const matches = cellInfo.matches; if (Array.isArray(matches)) { - - const firstLine = matches[0].range.startLineNumber; - const lastLine = matches[matches.length - 1].range.endLineNumber; - const lineTexts: string[] = []; - for (let i = firstLine; i <= lastLine; i++) { - if (cellInfo.cell.textModel) { - lineTexts.push(cellInfo.cell.textModel?.getLineContent(i)); + if (matches.length > 0) { + const lineTexts: string[] = []; + const firstLine = matches[0].range.startLineNumber; + const lastLine = matches[matches.length - 1].range.endLineNumber; + for (let i = firstLine; i <= lastLine; i++) { + if (cellInfo.cell.textModel) { + lineTexts.push(cellInfo.cell.textModel?.getLineContent(i)); + } } - } - - return new TextSearchMatch( - lineTexts.join('\n') + '\n', - matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), - previewOptions); + return new TextSearchMatch( + lineTexts.join('\n') + '\n', + matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), + previewOptions); + } } - else if (matches.searchPreviewInfo) { + else { return new TextSearchMatch( matches.searchPreviewInfo.line, - new Range(0, matches.searchPreviewInfo.range.start, 1, matches.searchPreviewInfo.range.end), + new Range(0, matches.searchPreviewInfo.range.start, 0, matches.searchPreviewInfo.range.end), previewOptions); } - - - const ranges = new SearchRange(0, 0, 1, 1); - return new TextSearchMatch( - '\n', - ranges, - previewOptions); + return undefined; } -export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], editorWidget: NotebookEditorWidget, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { +export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { let previousEndLine = -1; const groupedMatches: CellFindMatchInfoForTextModel[] = []; let currentMatches: FindMatch[] = []; cellFindMatches.forEach((cellFindMatch) => { - cellFindMatch.matches.forEach((match) => { - if (match instanceof FindMatch) { - - if (match.range.startLineNumber !== previousEndLine) { - currentMatches = []; - groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); - } - - currentMatches.push(match); - previousEndLine = match.range.endLineNumber; - } else { - groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); + cellFindMatch.contentMatches.forEach((match) => { + if (match.range.startLineNumber !== previousEndLine) { + currentMatches = []; + groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); } + + currentMatches.push(match); + previousEndLine = match.range.endLineNumber; }); currentMatches = []; groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); + + cellFindMatch.webviewMatches.forEach((match) => { + groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); + }); }); return groupedMatches.map(sameLineMatches => { - - return notebookEditorMatchToTextSearchResult(sameLineMatches, editorWidget, previewOptions); - }).filter((item): item is TextSearchMatch => !!item); + return notebookEditorMatchToTextSearchResult(sameLineMatches, previewOptions); + }).filter((elem): elem is TextSearchMatch => !!elem); } // export function addContextToNotebookEditorMatches(matches: ITextSearchMatch[], editorWidget: NotebookEditorWidget, query: ITextQuery): ITextSearchResult[] { From e37722ed071d783db33970ac0f42e0a97453ee43 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 29 Nov 2022 16:33:25 -0800 Subject: [PATCH 08/35] rough draft of notebook search --- .../browser/contrib/find/findModel.ts | 2 +- .../notebook/browser/notebookBrowser.ts | 2 +- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../browser/services/notebookEditorService.ts | 2 +- .../services/notebookEditorServiceImpl.ts | 10 + .../browser/view/renderers/webviewPreloads.ts | 6 +- .../contrib/search/browser/searchModel.ts | 218 ++++++++++++++++-- .../search/browser/searchNotebookHelpers.ts | 91 ++++---- .../contrib/search/browser/searchView.ts | 16 +- .../browser/links/terminalLinkOpeners.test.ts | 2 +- .../services/search/browser/searchService.ts | 5 +- .../services/search/common/search.ts | 5 +- .../services/search/common/searchService.ts | 44 +++- 13 files changed, 323 insertions(+), 82 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 0419aa607fc88..9a6147ea54202 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -491,7 +491,7 @@ export class FindModel extends Disposable { this.clearCurrentFindMatchDecoration(); const match = this._findMatches[cellIndex].getMatch(matchIndex) as CellWebviewFindMatch; - const offset = await this._notebookEditor.highlightFind(cell, match.index); + const offset = await this._notebookEditor.highlightFind(match.index); this._currentMatchDecorations = { kind: 'output', index: match.index }; this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 44d318c17086d..f6019312b2b9b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -637,7 +637,7 @@ export interface INotebookEditor { getNextVisibleCellIndex(index: number): number | undefined; getPreviousVisibleCellIndex(index: number): number | undefined; find(query: string, options: INotebookSearchOptions, token: CancellationToken): Promise; - highlightFind(cell: ICellViewModel, matchIndex: number): Promise; + highlightFind(matchIndex: number): Promise; unHighlightFind(matchIndex: number): Promise; findStop(): void; showProgress(): void; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 9cc761c679116..61c29f7435b56 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2464,7 +2464,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return ret; } - async highlightFind(cell: CodeCellViewModel, matchIndex: number): Promise { + async highlightFind(matchIndex: number): Promise { if (!this._webview) { return 0; } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index 2e7b1453f42b4..97bd92d99856b 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -23,7 +23,7 @@ export interface INotebookEditorService { retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension): IBorrowValue; retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; - + retrieveAllExistingWidgets(): IBorrowValue[]; onDidAddNotebookEditor: Event; onDidRemoveNotebookEditor: Event; onDidAddNotebookEditorWidget: Event; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index c9069cf5a8100..a1fbf03208a9c 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -143,6 +143,16 @@ export class NotebookEditorWidgetService implements INotebookEditorService { return undefined; } + retrieveAllExistingWidgets(): IBorrowValue[] { + const ret: IBorrowValue[] = []; + for (const widgetInfo of this._borrowableEditors.values()) { + for (const widget of widgetInfo.values()) { + ret.push(this._createBorrowValue(widget.token!, widget)); + } + } + return ret; + } + retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, initialDimension?: Dimension): IBorrowValue { let value = this._borrowableEditors.get(group.id)?.get(input.resource); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 0315144e1dc32..8ac1c97a597d8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -973,6 +973,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (match) { let offset = 0; try { + console.log(match.id); const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; @@ -1082,7 +1083,6 @@ async function webviewPreloads(ctx: PreloadContext) { const root = outputNode.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; if (shadowSelection && shadowSelection.anchorNode) { - console.log(extractSelectionLine(shadowSelection)); matches.push({ type: 'output', id: outputNode.id, @@ -1102,8 +1102,6 @@ async function webviewPreloads(ctx: PreloadContext) { // Optimization: avoid searching for the output container if (lastEl && lastEl.container.contains(anchorNode) && options.includeOutput) { - console.log('here1'); - console.log(lastEl); matches.push({ type: lastEl.type, id: lastEl.id, @@ -1125,8 +1123,6 @@ async function webviewPreloads(ctx: PreloadContext) { // inside output const cellId = node.parentElement?.parentElement?.id; if (cellId) { - console.log('here2'); - console.log(node); matches.push({ type: 'output', id: node.id, diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 98a34d2ea8c4d..37d601c5b7693 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -20,21 +20,22 @@ import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/model'; +import { FindDecorations } from 'vs/editor/contrib/find/browser/findDecorations'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; +import { minimapFindMatch, overviewRulerFindMatchForeground, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; -import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { notebookEditorMatchesToTextSearchResults, NotebookMatchInfo, NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; // import { addContextToNotebookEditorMatches, notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; @@ -43,15 +44,15 @@ import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/ export class Match { private static readonly MAX_PREVIEW_CHARS = 250; - - private _id: string; - private _range: Range; + // need to make sure that IDs are different for same-line-different-cell stuff + protected _id: string; + protected _range: Range; private _oneLinePreviewText: string; private _rangeInPreviewText: ISearchRange; // For replace private _fullPreviewRange: ISearchRange; - constructor(private _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange) { + constructor(protected _parent: FileMatch, private _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange) { this._oneLinePreviewText = _fullPreviewLines[_fullPreviewRange.startLineNumber]; const adjustedEndCol = _fullPreviewRange.startLineNumber === _fullPreviewRange.endLineNumber ? _fullPreviewRange.endColumn : @@ -166,6 +167,47 @@ export class Match { } } +export class NotebookMatch extends Match { + constructor(_parent: FileMatch, _fullPreviewLines: string[], _fullPreviewRange: ISearchRange, _documentRange: ISearchRange, private _notebookMatchInfo: NotebookMatchInfo) { + super(_parent, _fullPreviewLines, _fullPreviewRange, _documentRange); + + this._id = this._parent.id() + '>' + this._notebookMatchInfo.cellIndex + '_' + this.notebookMatchTypeString() + this.getRangeString() + this._range + this.getMatchString(); + } + + private notebookMatchTypeString(): string { + return this.isWebviewMatch() ? 'webview' : 'content'; + } + + private getRangeString(): string { + return `[${this._notebookMatchInfo.matchStartIndex},${this._notebookMatchInfo.matchStartIndex}]`; + } + + public isWebviewMatch() { + return this._notebookMatchInfo.webviewMatchInfo !== undefined; + } + + get cellIndex() { + return this._notebookMatchInfo.cellIndex; + } + + get matchStartIndex() { + return this._notebookMatchInfo.matchStartIndex; + } + + get matchEndIndex() { + return this._notebookMatchInfo.matchEndIndex; + } + + get webviewIndex() { + return this._notebookMatchInfo.webviewMatchInfo?.index; + } + + get cell() { + return this._notebookMatchInfo.cell; + } +} + + export class FileMatch extends Disposable implements IFileMatch { private static readonly _CURRENT_FIND_MATCH = ModelDecorationOptions.register({ @@ -220,6 +262,8 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; + private _currentMatchCellDecorations: string[] = []; + private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; private _context: Map = new Map(); public get context(): Map { @@ -376,13 +420,9 @@ export class FileMatch extends Disposable implements IFileMatch { } private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { - if (!this._notebookEditorWidget) { - return; - } - const textSearchResults = notebookEditorMatchesToTextSearchResults(matches, this._previewOptions); textSearchResults.forEach(textSearchResult => { - textSearchResultToMatches(textSearchResult, this).forEach(match => { + textSearchResultToNotebookMatches(textSearchResult, this).forEach(match => { if (!this._removedMatches.has(match.id())) { this.add(match); if (this.isMatchSelected(match)) { @@ -451,7 +491,9 @@ export class FileMatch extends Disposable implements IFileMatch { } matches(): Match[] { - return Array.from(this._matches.values()); + const vals = this._matches.values(); + const arr = Array.from(vals); + return arr; } remove(matches: Match | Match[]): void { @@ -551,6 +593,113 @@ export class FileMatch extends Disposable implements IFileMatch { this._onDispose.fire(); super.dispose(); } + + public async showMatch(match: NotebookMatch) { + const offset = await this.highlightCurrentFindMatchDecoration(match); + await this.revealCellRange(match, offset); + } + + private async highlightCurrentFindMatchDecoration(match: NotebookMatch): Promise { + if (!this._notebookEditorWidget) { + return Promise.resolve(null); + } + // needs + // notebook cell + // only cellmatch: + // match range + // only webviewmatch: + // + + if (!match.webviewIndex) { + this.clearCurrentFindMatchDecoration(); + // match is an editor FindMatch, we update find match decoration in the editor + // we will highlight the match in the webview + this._notebookEditorWidget.changeModelDecorations(accessor => { + const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION; + + const decorations: IModelDeltaDecoration[] = [ + { range: match.range(), options: findMatchesOptions } + ]; + const deltaDecoration: ICellModelDeltaDecorations = { + ownerId: match.cell.handle, + decorations: decorations + }; + + this._currentMatchDecorations = { + kind: 'input', + decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], [deltaDecoration]) + }; + }); + + this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, [{ + ownerId: match.cell.handle, + handle: match.cell.handle, + options: { + overviewRuler: { + color: overviewRulerSelectionHighlightForeground, + modelRanges: [match.range()], + includeOutput: false + } + } + } as INotebookDeltaDecoration]); + + return null; + } else { + this.clearCurrentFindMatchDecoration(); + const offset = await this._notebookEditorWidget.highlightFind(match.webviewIndex); + this._currentMatchDecorations = { kind: 'output', index: match.webviewIndex }; + + this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, [{ + ownerId: match.cell.handle, + handle: match.cell.handle, + options: { + overviewRuler: { + color: overviewRulerSelectionHighlightForeground, + modelRanges: [], + includeOutput: true + } + } + } as INotebookDeltaDecoration]); + + return offset; + } + } + + private clearCurrentFindMatchDecoration() { + if (!this._notebookEditorWidget) { + return; + } + + if (this._currentMatchDecorations?.kind === 'input') { + this._notebookEditorWidget.changeModelDecorations(accessor => { + accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); + this._currentMatchDecorations = null; + }); + } else if (this._currentMatchDecorations?.kind === 'output') { + this._notebookEditorWidget.unHighlightFind(this._currentMatchDecorations.index); + } + + this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, []); + } + + private revealCellRange(match: NotebookMatch, outputOffset: number | null) { + if (!this._notebookEditorWidget) { + return; + } + if (!match.webviewIndex) { + // reveal output range + this._notebookEditorWidget.focusElement(match.cell); + const index = this._notebookEditorWidget.getCellIndex(match.cell); + if (index !== undefined) { + // const range: ICellRange = { start: index, end: index + 1 }; + this._notebookEditorWidget.revealCellOffsetInCenterAsync(match.cell, outputOffset ?? 0); + } + } else { + this._notebookEditorWidget.focusElement(match.cell); + this._notebookEditorWidget.setCellEditorSelection(match.cell, match.range()); + this._notebookEditorWidget.revealRangeInCenterIfOutsideViewportAsync(match.cell, match.range()); + } + } } export interface IChangeEvent { @@ -1098,6 +1247,10 @@ export function searchMatchComparer(elementA: RenderableMatch, elementB: Rendera } } + if (elementA instanceof NotebookMatch && elementB instanceof NotebookMatch) { + return compareNotebookPos(elementA, elementB); + } + if (elementA instanceof Match && elementB instanceof Match) { return Range.compareRangesUsingStarts(elementA.range(), elementB.range()); } @@ -1105,6 +1258,27 @@ export function searchMatchComparer(elementA: RenderableMatch, elementB: Rendera return 0; } +export function compareNotebookPos(match1: NotebookMatch, match2: NotebookMatch): number { + if (match1.cellIndex === match2.cellIndex) { + if (match1.matchStartIndex === match2.matchStartIndex) { + if (match1.matchEndIndex === match2.matchEndIndex) { + return 0; + } else if (match1.matchEndIndex < match2.matchEndIndex) { + return -1; + } else { + return 1; + } + } else if (match1.matchStartIndex < match2.matchStartIndex) { + return -1; + } else { + return 1; + } + } else if (match1.cellIndex < match2.cellIndex) { + return -1; + } else { + return 1; + } +} export function searchComparer(elementA: RenderableMatch, elementB: RenderableMatch, sortOrder: SearchSortOrder = SearchSortOrder.Default): number { const elemAParents = createParentList(elementA); const elemBParents = createParentList(elementB); @@ -1174,9 +1348,6 @@ export class SearchResult extends Disposable { this._isDirty = !this.isEmpty(); } })); - - - // this._notebookEditors = new Set(); } async batchReplace(elementsToReplace: RenderableMatch[]) { @@ -1830,6 +2001,21 @@ function textSearchResultToMatches(rawMatch: ITextSearchMatch, fileMatch: FileMa } } +function textSearchResultToNotebookMatches(rawMatch: NotebookTextSearchMatch, fileMatch: FileMatch): NotebookMatch[] { + const previewLines = rawMatch.preview.text.split('\n'); + if (Array.isArray(rawMatch.ranges)) { + + return rawMatch.ranges.map((r, i) => { + const previewRange: ISearchRange = (rawMatch.preview.matches)[i]; + return new NotebookMatch(fileMatch, previewLines, previewRange, r, rawMatch.notebookMatchInfo); + }); + } else { + const previewRange = rawMatch.preview.matches; + const match = new NotebookMatch(fileMatch, previewLines, previewRange, rawMatch.ranges, rawMatch.notebookMatchInfo); + return [match]; + } +} + export function arrayContainsElementOrParent(element: RenderableMatch, testArray: RenderableMatch[]): boolean { do { if (testArray.includes(element)) { diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index 24823acfdae48..dad92876e1d5a 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -6,17 +6,32 @@ import { FindMatch } from 'vs/editor/common/model'; import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { ITextSearchPreviewOptions, TextSearchMatch } from 'vs/workbench/services/search/common/search'; +import { ISearchRange, ITextSearchPreviewOptions, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { Range } from 'vs/editor/common/core/range'; -interface CellFindMatchInfoForTextModel { +export interface NotebookMatchInfo { + cellIndex: number; + matchStartIndex: number; + matchEndIndex: number; cell: ICellViewModel; + webviewMatchInfo?: { + index: number; + }; +} + +interface CellFindMatchInfoForTextModel { + notebookMatchInfo: NotebookMatchInfo; matches: FindMatch[] | CellWebviewFindMatch; } -function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch | undefined { - const matches = cellInfo.matches; +export class NotebookTextSearchMatch extends TextSearchMatch { + constructor(text: string, range: ISearchRange | ISearchRange[], public notebookMatchInfo: NotebookMatchInfo, previewOptions?: ITextSearchPreviewOptions) { + super(text, range, previewOptions); + } +} +function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTextModel, previewOptions?: ITextSearchPreviewOptions): NotebookTextSearchMatch | undefined { + const matches = cellInfo.matches; if (Array.isArray(matches)) { if (matches.length > 0) { @@ -24,36 +39,39 @@ function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTex const firstLine = matches[0].range.startLineNumber; const lastLine = matches[matches.length - 1].range.endLineNumber; for (let i = firstLine; i <= lastLine; i++) { - if (cellInfo.cell.textModel) { - lineTexts.push(cellInfo.cell.textModel?.getLineContent(i)); - } + lineTexts.push(cellInfo.notebookMatchInfo.cell.textBuffer.getLineContent(i)); } - return new TextSearchMatch( + return new NotebookTextSearchMatch( lineTexts.join('\n') + '\n', matches.map(m => new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endLineNumber - 1, m.range.endColumn - 1)), + cellInfo.notebookMatchInfo, previewOptions); } } else { - return new TextSearchMatch( + return new NotebookTextSearchMatch( matches.searchPreviewInfo.line, new Range(0, matches.searchPreviewInfo.range.start, 0, matches.searchPreviewInfo.range.end), + cellInfo.notebookMatchInfo, previewOptions); } return undefined; } -export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], previewOptions?: ITextSearchPreviewOptions): TextSearchMatch[] { +export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFindMatchWithIndex[], previewOptions?: ITextSearchPreviewOptions): NotebookTextSearchMatch[] { let previousEndLine = -1; const groupedMatches: CellFindMatchInfoForTextModel[] = []; let currentMatches: FindMatch[] = []; + let startIndexOfCurrentMatches = 0; cellFindMatches.forEach((cellFindMatch) => { - cellFindMatch.contentMatches.forEach((match) => { + const cellIndex = cellFindMatch.index; + cellFindMatch.contentMatches.forEach((match, index) => { if (match.range.startLineNumber !== previousEndLine) { currentMatches = []; - groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); + groupedMatches.push({ matches: currentMatches, notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: index, cell: cellFindMatch.cell } }); + startIndexOfCurrentMatches = cellIndex + 1; } currentMatches.push(match); @@ -61,50 +79,31 @@ export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFi }); currentMatches = []; - groupedMatches.push({ cell: cellFindMatch.cell, matches: currentMatches }); + groupedMatches.push({ matches: currentMatches, notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: cellFindMatch.contentMatches.length - 1, cell: cellFindMatch.cell } }); - cellFindMatch.webviewMatches.forEach((match) => { - groupedMatches.push({ cell: cellFindMatch.cell, matches: match }); + cellFindMatch.webviewMatches.forEach((match, index) => { + groupedMatches.push({ matches: match, notebookMatchInfo: { cellIndex, matchStartIndex: index, matchEndIndex: index, cell: cellFindMatch.cell, webviewMatchInfo: { index: match.index } } }); }); }); return groupedMatches.map(sameLineMatches => { return notebookEditorMatchToTextSearchResult(sameLineMatches, previewOptions); - }).filter((elem): elem is TextSearchMatch => !!elem); + }).filter((elem): elem is NotebookTextSearchMatch => !!elem); } -// export function addContextToNotebookEditorMatches(matches: ITextSearchMatch[], editorWidget: NotebookEditorWidget, query: ITextQuery): ITextSearchResult[] { +// export function addContextToNotebookEditorMatches(cellFindMatches: CellFindMatchWithIndex[], query: ITextQuery): ITextSearchResult[] { // const results: ITextSearchResult[] = []; -// let prevLine = -1; -// for (let i = 0; i < matches.length; i++) { -// const { start: matchStartLine, end: matchEndLine } = getMatchStartEnd(matches[i]); -// if (typeof query.beforeContext === 'number' && query.beforeContext > 0) { -// const beforeContextStartLine = Math.max(prevLine + 1, matchStartLine - query.beforeContext); -// for (let b = beforeContextStartLine; b < matchStartLine; b++) { -// results.push({ -// text: model.getLineContent(b + 1), -// lineNumber: b -// }); -// } -// } - -// results.push(matches[i]); - -// const nextMatch = matches[i + 1]; -// const nextMatchStartLine = nextMatch ? getMatchStartEnd(nextMatch).start : Number.MAX_VALUE; -// if (typeof query.afterContext === 'number' && query.afterContext > 0) { -// const afterContextToLine = Math.min(nextMatchStartLine - 1, matchEndLine + query.afterContext, model.getLineCount() - 1); -// for (let a = matchEndLine + 1; a <= afterContextToLine; a++) { -// results.push({ -// text: model.getLineContent(a + 1), -// lineNumber: a -// }); -// } -// } - -// prevLine = matchEndLine; -// } +// // we need a cell and all of its corresponding textmatches +// // ^ we need this split by webview and content matches + +// // +// cellFindMatches.forEach(findMatch => { +// findMatch.contentMatches.forEach(contentMatch => { +// const result = addContextToEditorMatches() +// }) +// }) + // return results; // } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index e03faef512fe6..03aaae8497ca9 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -73,7 +73,7 @@ import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, IChangeEvent, ISearchWorkbenchService, Match, NotebookMatch, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; @@ -1797,8 +1797,18 @@ export class SearchView extends ViewPane { if (editor instanceof NotebookEditor) { const controller = editor.getControl()?.getContribution(NotebookFindContrib.id); - const matchIndex = element instanceof Match ? element.parent().matches().findIndex(e => e.id() === element.id()) : undefined; - controller?.show(this.searchWidget.searchInput.getValue(), { matchIndex, focus: false }); + if (element instanceof Match) { + const matchIndex = element.parent().matches().findIndex(e => e.id() === element.id()); + if (element instanceof NotebookMatch) { + + // no op for now! + element.parent().showMatch(element); + } else { + + controller?.show(this.searchWidget.searchInput.getValue(), { matchIndex, focus: false }); + } + } + } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index 528ca7290a2f0..d45523d993920 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -77,7 +77,7 @@ suite('Workbench - TerminalLinkOpeners', () => { setup(() => { instantiationService = new TestInstantiationService(); fileService = new TestFileService(new NullLogService()); - searchService = new TestSearchService(null!, null!, null!, null!, null!, null!, null!); + searchService = new TestSearchService(null!, null!, null!, null!, null!, null!, null!, null!); instantiationService.set(IFileService, fileService); instantiationService.set(ILogService, new NullLogService()); instantiationService.set(ISearchService, searchService); diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index bea85f71a437d..4b6affdfce96d 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -27,6 +27,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; + export class RemoteSearchService extends SearchService { constructor( @IModelService modelService: IModelService, @@ -37,8 +39,9 @@ export class RemoteSearchService extends SearchService { @IFileService fileService: IFileService, @IInstantiationService readonly instantiationService: IInstantiationService, @IUriIdentityService uriIdentityService: IUriIdentityService, + @INotebookEditorService notebookEditorService: INotebookEditorService ) { - super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService); + super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService, notebookEditorService); const searchProvider = this.instantiationService.createInstance(LocalFileSearchWorkerClient); this.registerSearchResultProvider(Schemas.file, SearchProviderType.file, searchProvider); this.registerSearchResultProvider(Schemas.file, SearchProviderType.text, searchProvider); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 1d0cdb843fc1c..a12c62f5e4817 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -181,7 +181,6 @@ export interface ITextSearchMatch { uri?: URI; ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; - cellFragment?: string; } export interface ITextSearchContext { @@ -275,11 +274,9 @@ export class FileMatch implements IFileMatch { export class TextSearchMatch implements ITextSearchMatch { ranges: ISearchRange | ISearchRange[]; preview: ITextSearchResultPreview; - cellFragment?: string; - constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions, cellFragment?: string) { + constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { this.ranges = range; - this.cellFragment = cellFragment; // Trim preview if this is one match and a single-line match with a preview requested. // Otherwise send the full text, like for replace or for showing multiple previews. diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 874de93dad5e7..de72198944c5d 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -19,6 +19,10 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; + +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; + import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; @@ -44,6 +48,7 @@ export class SearchService extends Disposable implements ISearchService { @IExtensionService private readonly extensionService: IExtensionService, @IFileService private readonly fileService: IFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); } @@ -75,7 +80,7 @@ export class SearchService extends Disposable implements ISearchService { async textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { // Get local results from dirty/untitled - const localResults = this.getLocalResults(query); + const localResults = await this.getLocalResults(query); if (onProgress) { arrays.coalesce([...localResults.results.values()]).forEach(onProgress); @@ -404,7 +409,7 @@ export class SearchService extends Disposable implements ISearchService { } } - private getLocalResults(query: ITextQuery): { results: ResourceMap; limitHit: boolean } { + private async getLocalResults(query: ITextQuery): Promise<{ results: ResourceMap; limitHit: boolean }> { const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); let limitHit = false; @@ -473,6 +478,41 @@ export class SearchService extends Disposable implements ISearchService { localResults.set(originalResource, null); } }); + + + const notebookWidgets = this.notebookEditorService.retrieveAllExistingWidgets(); + for (const borrowWidget of notebookWidgets) { + const widget = borrowWidget.value; + if (!widget || !widget.viewModel) { + continue; + } + const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; + let matches = await widget + .find(query.contentPattern.pattern, { + regex: query.contentPattern.isRegExp, + wholeWord: query.contentPattern.isWordMatch, + caseSensitive: query.contentPattern.isCaseSensitive, + includeMarkupInput: false, + includeMarkupPreview: true, + includeCodeInput: true, + includeOutput: true, + }, CancellationToken.None); + + + if (matches.length) { + if (askMax && matches.length >= askMax) { + limitHit = true; + matches = matches.slice(0, askMax - 1); + } + + const fileMatch = new FileMatch(widget.viewModel.uri); + localResults.set(widget.viewModel.uri, fileMatch); + + fileMatch.results = notebookEditorMatchesToTextSearchResults(matches, query.previewOptions); + } else { + localResults.set(widget.viewModel.uri, null); + } + } } return { From d20ea1d394b0d571ba20f508b12dbeb991c39c72 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 2 Dec 2022 13:15:47 -0800 Subject: [PATCH 09/35] move local notebook search out of searchservice --- .../contrib/search/browser/searchModel.ts | 99 ++++++++++++++++--- .../browser/links/terminalLinkOpeners.test.ts | 2 +- .../services/search/browser/searchService.ts | 6 +- .../services/search/common/search.ts | 2 +- .../services/search/common/searchService.ts | 78 +++++++-------- 5 files changed, 130 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 37d601c5b7693..27e56b0768096 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as arrays from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { compareFileExtensions, compareFileNames, comparePaths } from 'vs/base/common/comparers'; @@ -11,10 +12,11 @@ import * as errors from 'vs/base/common/errors'; import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; +import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { lcut } from 'vs/base/common/strings'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; +import { isNumber } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; @@ -38,7 +40,7 @@ import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { notebookEditorMatchesToTextSearchResults, NotebookMatchInfo, NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; // import { addContextToNotebookEditorMatches, notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class Match { @@ -1693,7 +1695,9 @@ export class SearchModel extends Disposable { @ISearchService private readonly searchService: ISearchService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); this._searchResult = this.instantiationService.createInstance(SearchResult, this); @@ -1735,6 +1739,84 @@ export class SearchModel extends Disposable { return this._searchResult; } + private async getLocalNotebookResults(query: ITextQuery): Promise<{ results: ResourceMap; limitHit: boolean }> { + const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); + let limitHit = false; + + if (query.type === QueryType.Text) { + const notebookWidgets = this.notebookEditorService.retrieveAllExistingWidgets(); + for (const borrowWidget of notebookWidgets) { + const widget = borrowWidget.value; + if (!widget || !widget.viewModel) { + continue; + } + + const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; + let matches = await widget + .find(query.contentPattern.pattern, { + regex: query.contentPattern.isRegExp, + wholeWord: query.contentPattern.isWordMatch, + caseSensitive: query.contentPattern.isCaseSensitive, + includeMarkupInput: false, + includeMarkupPreview: true, + includeCodeInput: true, + includeOutput: true, + }, CancellationToken.None); + + + if (matches.length) { + if (askMax && matches.length >= askMax) { + limitHit = true; + matches = matches.slice(0, askMax - 1); + } + const fileMatch = { resource: widget.viewModel.uri, results: notebookEditorMatchesToTextSearchResults(matches, query.previewOptions) }; + localResults.set(widget.viewModel.uri, fileMatch); + } else { + localResults.set(widget.viewModel.uri, null); + } + } + } + + return { + results: localResults, + limitHit + }; + } + + async notebookSearch(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { + const localResults = await this.getLocalNotebookResults(query); + + if (onProgress) { + arrays.coalesce([...localResults.results.values()]).forEach(onProgress); + } + + return { + messages: [], + limitHit: localResults.limitHit, + results: arrays.coalesce([...localResults.results.values()]) + }; + } + + + private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, onProgress?: (result: ISearchProgressItem) => void) { + const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); + const onProgressCall = (p: ISearchProgressItem) => { + progressEmitter.fire(); + this.onSearchProgress(p); + + onProgress?.(p); + }; + const notebookResult = await this.notebookSearch(query, onProgressCall); + const currentResult = await this.searchService.textSearch( + searchQuery, + this.currentCancelTokenSource.token, onProgressCall, + new ResourceSet(notebookResult.results.map(r => r.resource, this.uriIdentityService.extUri.ignorePathCasing), uri => this.uriIdentityService.extUri.getComparisonKey(uri)) + ); + tokenSource.dispose(); + return { ...currentResult, ...notebookResult }; + + } + async search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(true); @@ -1751,16 +1833,7 @@ export class SearchModel extends Disposable { // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 150 : 0)); - const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); - const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { - progressEmitter.fire(); - this.onSearchProgress(p); - - onProgress?.(p); - }); - - const dispose = () => tokenSource.dispose(); - currentRequest.then(dispose, dispose); + const currentRequest = this.doSearch(query, progressEmitter, this._searchQuery, onProgress); const start = Date.now(); diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index d45523d993920..528ca7290a2f0 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -77,7 +77,7 @@ suite('Workbench - TerminalLinkOpeners', () => { setup(() => { instantiationService = new TestInstantiationService(); fileService = new TestFileService(new NullLogService()); - searchService = new TestSearchService(null!, null!, null!, null!, null!, null!, null!, null!); + searchService = new TestSearchService(null!, null!, null!, null!, null!, null!, null!); instantiationService.set(IFileService, fileService); instantiationService.set(ILogService, new NullLogService()); instantiationService.set(ISearchService, searchService); diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index 4b6affdfce96d..74a704e331a43 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -27,7 +27,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +// import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; export class RemoteSearchService extends SearchService { constructor( @@ -39,9 +39,9 @@ export class RemoteSearchService extends SearchService { @IFileService fileService: IFileService, @IInstantiationService readonly instantiationService: IInstantiationService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @INotebookEditorService notebookEditorService: INotebookEditorService + // @INotebookEditorService notebookEditorService: INotebookEditorService ) { - super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService, notebookEditorService); + super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService); const searchProvider = this.instantiationService.createInstance(LocalFileSearchWorkerClient); this.registerSearchResultProvider(Schemas.file, SearchProviderType.file, searchProvider); this.registerSearchResultProvider(Schemas.file, SearchProviderType.text, searchProvider); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index a12c62f5e4817..27f3b8a1a75ee 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -41,7 +41,7 @@ export const ISearchService = createDecorator('searchService'); */ export interface ISearchService { readonly _serviceBrand: undefined; - textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; + textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void, notebookURIs?: Set): Promise; fileSearch(query: IFileQuery, token?: CancellationToken): Promise; clearCache(cacheKey: string): Promise; registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable; diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index de72198944c5d..74441e3cf08b4 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -8,7 +8,7 @@ import { DeferredPromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap } from 'vs/base/common/map'; +import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { StopWatch } from 'vs/base/common/stopwatch'; import { isNumber } from 'vs/base/common/types'; @@ -20,8 +20,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; -import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +// import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +// import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -48,7 +48,7 @@ export class SearchService extends Disposable implements ISearchService { @IExtensionService private readonly extensionService: IExtensionService, @IFileService private readonly fileService: IFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + // @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); } @@ -78,7 +78,7 @@ export class SearchService extends Disposable implements ISearchService { }); } - async textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { + async textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { // Get local results from dirty/untitled const localResults = await this.getLocalResults(query); @@ -89,7 +89,7 @@ export class SearchService extends Disposable implements ISearchService { const onProviderProgress = (progress: ISearchProgressItem) => { if (isFileMatch(progress)) { // Match - if (!localResults.results.has(progress.resource) && onProgress) { // don't override local results + if (!localResults.results.has(progress.resource) && !(notebookURIs && notebookURIs.has(progress.resource)) && onProgress) { // don't override local results onProgress(progress); } } else if (onProgress) { @@ -480,39 +480,39 @@ export class SearchService extends Disposable implements ISearchService { }); - const notebookWidgets = this.notebookEditorService.retrieveAllExistingWidgets(); - for (const borrowWidget of notebookWidgets) { - const widget = borrowWidget.value; - if (!widget || !widget.viewModel) { - continue; - } - const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; - let matches = await widget - .find(query.contentPattern.pattern, { - regex: query.contentPattern.isRegExp, - wholeWord: query.contentPattern.isWordMatch, - caseSensitive: query.contentPattern.isCaseSensitive, - includeMarkupInput: false, - includeMarkupPreview: true, - includeCodeInput: true, - includeOutput: true, - }, CancellationToken.None); - - - if (matches.length) { - if (askMax && matches.length >= askMax) { - limitHit = true; - matches = matches.slice(0, askMax - 1); - } - - const fileMatch = new FileMatch(widget.viewModel.uri); - localResults.set(widget.viewModel.uri, fileMatch); - - fileMatch.results = notebookEditorMatchesToTextSearchResults(matches, query.previewOptions); - } else { - localResults.set(widget.viewModel.uri, null); - } - } + // const notebookWidgets = this.notebookEditorService.retrieveAllExistingWidgets(); + // for (const borrowWidget of notebookWidgets) { + // const widget = borrowWidget.value; + // if (!widget || !widget.viewModel) { + // continue; + // } + // const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; + // let matches = await widget + // .find(query.contentPattern.pattern, { + // regex: query.contentPattern.isRegExp, + // wholeWord: query.contentPattern.isWordMatch, + // caseSensitive: query.contentPattern.isCaseSensitive, + // includeMarkupInput: false, + // includeMarkupPreview: true, + // includeCodeInput: true, + // includeOutput: true, + // }, CancellationToken.None); + + + // if (matches.length) { + // if (askMax && matches.length >= askMax) { + // limitHit = true; + // matches = matches.slice(0, askMax - 1); + // } + + // const fileMatch = new FileMatch(widget.viewModel.uri); + // localResults.set(widget.viewModel.uri, fileMatch); + + // fileMatch.results = notebookEditorMatchesToTextSearchResults(matches, query.previewOptions); + // } else { + // localResults.set(widget.viewModel.uri, null); + // } + // } } return { From 188ee99c6bb057c9a98a1c93c2b5916955b75f2d Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 17 Jan 2023 14:49:52 -0800 Subject: [PATCH 10/35] working replace --- .../browser/view/renderers/webviewPreloads.ts | 54 +++++++++++++++++-- .../contrib/search/browser/replaceService.ts | 35 ++++++++++-- .../browser/searchActionsRemoveReplace.ts | 4 +- .../contrib/search/browser/searchModel.ts | 40 +++++++++----- .../search/browser/searchNotebookHelpers.ts | 12 +++-- .../contrib/search/browser/searchView.ts | 2 +- 6 files changed, 116 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 8ac1c97a597d8..7be700c47f248 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1002,18 +1002,24 @@ async function webviewPreloads(ctx: PreloadContext) { function extractSelectionLine(selection: Selection): ISearchPreviewInfo { const range = selection.getRangeAt(0); - const oldRange = document.createRange(); - oldRange.setStart(range.startContainer, range.startOffset); - oldRange.setEnd(range.endContainer, range.endOffset); + const oldRange = range.cloneRange(); + const captureLength = selection.toString().length; + // selection.collapseToStart(); + // oldRange.setStart(range.startContainer, range.startOffset); + // oldRange.setEnd(range.endContainer, range.endOffset); selection.modify('move', 'backward', 'lineboundary'); selection.modify('extend', 'forward', 'lineboundary'); const newRange = selection.getRangeAt(0); const line = selection.toString(); + const rangeStart = getStartOffset(newRange, oldRange); + // const rangeStart = oldRange.startOffset - newRange.startOffset; + // const rangeEnd = oldRange.endOffset - newRange.startOffset; + // console.log(getStartOffset(newRange, oldRange)); const lineRange = { - start: oldRange.startOffset - newRange.startOffset, - end: oldRange.endOffset - newRange.startOffset, + start: rangeStart, + end: rangeStart + captureLength, }; // re-add the old range so that the selection is restored @@ -1023,6 +1029,44 @@ async function webviewPreloads(ctx: PreloadContext) { return { line, range: lineRange }; } + function getStartOffset(lineRange: Range, originalRange: Range) { + const firstCommonAncestor = findFirstCommonAncestor(lineRange.startContainer, originalRange.startContainer); + + const selectionOffset = getSelectionOffsetRelativeTo(firstCommonAncestor, lineRange.startContainer) + lineRange.startOffset; + const textOffset = getSelectionOffsetRelativeTo(firstCommonAncestor, originalRange.startContainer) + originalRange.startOffset; + return textOffset - selectionOffset; + } + + // modified from https://stackoverflow.com/a/68583466/16253823 + function findFirstCommonAncestor(nodeA: Node, nodeB: Node) { + const range = new Range(); + range.setStart(nodeA, 0); + range.setEnd(nodeB, 0); + return range.commonAncestorContainer; + } + + // modified from https://stackoverflow.com/a/48812529/16253823 + function getSelectionOffsetRelativeTo(parentElement: Node, currentNode: Node | null): number { + if (!currentNode) { + return 0; + } + let offset = 0; + + if (currentNode === parentElement || !parentElement.contains(currentNode)) { + return offset; + } + + let prevSibling = currentNode.previousSibling; + + while (prevSibling) { + const nodeContent = prevSibling.nodeValue || ''; + offset += nodeContent.length; + prevSibling = prevSibling.previousSibling; + } + + return offset + getSelectionOffsetRelativeTo(parentElement, currentNode.parentNode); + } + const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean }) => { let find = true; const matches: IFindMatch[] = []; diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 9d7dced2e8b2f..6c8137e21fb64 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -11,7 +11,7 @@ import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/browser/searchModel'; +import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService, NotebookMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -27,6 +27,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { Promises } from 'vs/base/common/async'; import { SaveSourceRegistry } from 'vs/workbench/common/editor'; +import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const REPLACE_PREVIEW = 'replacePreview'; @@ -109,7 +110,23 @@ export class ReplaceService implements IReplaceService { const edits = this.createEdits(arg, resource); await this.bulkEditorService.apply(edits, { progress }); - return Promises.settled(edits.map(async e => this.textFileService.files.get(e.resource)?.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }))); + const rawTextPromises = edits.map(async e => { + if (e.resource.scheme === network.Schemas.vscodeNotebookCell) { + const notebookResource = CellUri.parse(e.resource)?.notebook; + if (notebookResource) { + return this.editorService.save([...this.editorService.findEditors(notebookResource)]); + } else { + return Promise.resolve(); + } + } else { + return this.textFileService.files.get(e.resource)?.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); + } + }); + + + // const notebookPromises = edits.notebookEdits.map(async e => this.editorService.save([...this.editorService.findEditors(e.resource)])); + + return Promises.settled(rawTextPromises); } async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { @@ -177,8 +194,13 @@ export class ReplaceService implements IReplaceService { const edits: ResourceTextEdit[] = []; if (arg instanceof Match) { - const match = arg; - edits.push(this.createEdit(match, match.replaceString, resource)); + if (arg instanceof NotebookMatch) { + const match = arg; + edits.push(this.createEdit(match, match.replaceString, match.cell.uri)); + } else { + const match = arg; + edits.push(this.createEdit(match, match.replaceString, resource)); + } } if (arg instanceof FileMatch) { @@ -189,7 +211,9 @@ export class ReplaceService implements IReplaceService { arg.forEach(element => { const fileMatch = element; if (fileMatch.count() > 0) { - edits.push(...fileMatch.matches().map(match => this.createEdit(match, match.replaceString, resource))); + edits.push(...fileMatch.matches().map( + match => this.createEdit(match, match.replaceString, (match instanceof NotebookMatch) ? match.cell.uri : resource) + )); } }); } @@ -199,6 +223,7 @@ export class ReplaceService implements IReplaceService { private createEdit(match: Match, text: string, resource: URI | null = null): ResourceTextEdit { const fileMatch: FileMatch = match.parent(); + return new ResourceTextEdit( resource ?? fileMatch.resource, { range: match.range(), text }, undefined, undefined diff --git a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts index e318b33266a64..067dce68f4bd3 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts @@ -13,7 +13,7 @@ import { searchRemoveIcon, searchReplaceIcon } from 'vs/workbench/contrib/search import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; -import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; +import { arrayContainsElementOrParent, FileMatch, FolderMatch, Match, NotebookMatch, RenderableMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -310,7 +310,7 @@ function performReplace(accessor: ServicesAccessor, if (nextFocusElement instanceof Match) { const useReplacePreview = configurationService.getValue().search.useReplacePreview; - if (!useReplacePreview || hasToOpenFile(accessor, nextFocusElement)) { + if (!useReplacePreview || hasToOpenFile(accessor, nextFocusElement) || nextFocusElement instanceof NotebookMatch) { viewlet?.open(nextFocusElement, true); } else { accessor.get(IReplaceService).openReplacePreview(nextFocusElement, true); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 27e56b0768096..383204afb6cd2 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -36,6 +36,7 @@ import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecoratio // import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { notebookEditorMatchesToTextSearchResults, NotebookMatchInfo, NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; // import { addContextToNotebookEditorMatches, notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; @@ -263,6 +264,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _name: Lazy; private _updateScheduler: RunOnceScheduler; + private _notebookUpdateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; private _currentMatchCellDecorations: string[] = []; private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; @@ -289,6 +291,7 @@ export class FileMatch extends Disposable implements IFileMatch { this._matches = new Map(); this._removedMatches = new Set(); this._updateScheduler = new RunOnceScheduler(this.updateMatchesForModel.bind(this), 250); + this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); this._name = new Lazy(() => labelService.getUriBasenameLabel(this.resource)); this.createMatches(); } @@ -329,9 +332,14 @@ export class FileMatch extends Disposable implements IFileMatch { bindEditorWidget(widget: NotebookEditorWidget) { this._notebookEditorWidget = widget; - this._editorWidgetListener = this._notebookEditorWidget.viewModel?.onDidChangeViewCells(() => { - this._updateScheduler.schedule(); + + this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { + if (!e.rawEvents.some(event => event.kind === NotebookCellsChangeType.ChangeCellContent || event.kind === NotebookCellsChangeType.ModelChange)) { + return; + } + this._notebookUpdateScheduler.schedule(); }) ?? null; + this.updateHighlights(); console.log(`added widget ${this._notebookEditorWidget.textModel?.uri}`); } @@ -341,7 +349,7 @@ export class FileMatch extends Disposable implements IFileMatch { if (this._notebookEditorWidget) { this._updateScheduler.cancel(); this._model = null; - this._editorWidgetListener!.dispose(); + this._editorWidgetListener?.dispose(); } this._notebookEditorWidget = null; console.log(`removed widget ${widget.textModel?.uri}`); @@ -394,11 +402,11 @@ export class FileMatch extends Disposable implements IFileMatch { wholeWord: this._query.isWordMatch, caseSensitive: this._query.isCaseSensitive, wordSeparators: wordSeparators ?? undefined, - includeMarkupInput: false, - includeMarkupPreview: true, + includeMarkupInput: true, + includeMarkupPreview: false, includeCodeInput: true, - includeOutput: true, - }, CancellationToken.None); + includeOutput: false, + }, CancellationToken.None, true); this.updateNotebookMatches(allMatches, true); } @@ -419,6 +427,7 @@ export class FileMatch extends Disposable implements IFileMatch { const wordSeparators = this._query.isWordMatch && this._query.wordSeparators ? this._query.wordSeparators : null; const matches = this._model.findMatches(this._query.pattern, range, !!this._query.isRegExp, !!this._query.isCaseSensitive, wordSeparators, false, this._maxResults ?? Number.MAX_SAFE_INTEGER); this.updateMatches(matches, modelChange, this._model); + this.updateMatchesForEditorWidget(); } private updateNotebookMatches(matches: CellFindMatchWithIndex[], modelChange: boolean): void { @@ -598,7 +607,10 @@ export class FileMatch extends Disposable implements IFileMatch { public async showMatch(match: NotebookMatch) { const offset = await this.highlightCurrentFindMatchDecoration(match); - await this.revealCellRange(match, offset); + // if (this._notebookEditorWidget) { + // this._notebookEditorWidget.focusNotebookCell(match.cell, 'container', {}); + // } + this.revealCellRange(match, offset); } private async highlightCurrentFindMatchDecoration(match: NotebookMatch): Promise { @@ -612,7 +624,7 @@ export class FileMatch extends Disposable implements IFileMatch { // only webviewmatch: // - if (!match.webviewIndex) { + if (match.webviewIndex === undefined) { this.clearCurrentFindMatchDecoration(); // match is an editor FindMatch, we update find match decoration in the editor // we will highlight the match in the webview @@ -688,7 +700,7 @@ export class FileMatch extends Disposable implements IFileMatch { if (!this._notebookEditorWidget) { return; } - if (!match.webviewIndex) { + if (match.webviewIndex) { // reveal output range this._notebookEditorWidget.focusElement(match.cell); const index = this._notebookEditorWidget.getCellIndex(match.cell); @@ -697,7 +709,7 @@ export class FileMatch extends Disposable implements IFileMatch { this._notebookEditorWidget.revealCellOffsetInCenterAsync(match.cell, outputOffset ?? 0); } } else { - this._notebookEditorWidget.focusElement(match.cell); + this._notebookEditorWidget.focusNotebookCell(match.cell, 'editor'); this._notebookEditorWidget.setCellEditorSelection(match.cell, match.range()); this._notebookEditorWidget.revealRangeInCenterIfOutsideViewportAsync(match.cell, match.range()); } @@ -1757,10 +1769,10 @@ export class SearchModel extends Disposable { regex: query.contentPattern.isRegExp, wholeWord: query.contentPattern.isWordMatch, caseSensitive: query.contentPattern.isCaseSensitive, - includeMarkupInput: false, - includeMarkupPreview: true, + includeMarkupInput: true, + includeMarkupPreview: false, includeCodeInput: true, - includeOutput: true, + includeOutput: false, }, CancellationToken.None); diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index dad92876e1d5a..9a52633ecd746 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -69,8 +69,10 @@ export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFi const cellIndex = cellFindMatch.index; cellFindMatch.contentMatches.forEach((match, index) => { if (match.range.startLineNumber !== previousEndLine) { - currentMatches = []; - groupedMatches.push({ matches: currentMatches, notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: index, cell: cellFindMatch.cell } }); + if (currentMatches.length > 0) { + groupedMatches.push({ matches: [...currentMatches], notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: index, cell: cellFindMatch.cell } }); + currentMatches = []; + } startIndexOfCurrentMatches = cellIndex + 1; } @@ -78,8 +80,10 @@ export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFi previousEndLine = match.range.endLineNumber; }); - currentMatches = []; - groupedMatches.push({ matches: currentMatches, notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: cellFindMatch.contentMatches.length - 1, cell: cellFindMatch.cell } }); + if (currentMatches.length > 0) { + groupedMatches.push({ matches: [...currentMatches], notebookMatchInfo: { cellIndex, matchStartIndex: startIndexOfCurrentMatches, matchEndIndex: cellFindMatch.contentMatches.length - 1, cell: cellFindMatch.cell } }); + currentMatches = []; + } cellFindMatch.webviewMatches.forEach((match, index) => { groupedMatches.push({ matches: match, notebookMatchInfo: { cellIndex, matchStartIndex: index, matchEndIndex: index, cell: cellFindMatch.cell, webviewMatchInfo: { index: match.index } } }); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 03aaae8497ca9..056c6160b76a6 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1760,7 +1760,7 @@ export class SearchView extends ViewPane { private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; - return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) ? + return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(lineMatch instanceof NotebookMatch)) ? this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) : this.open(lineMatch, preserveFocus, sideBySide, pinned); } From 51c667e923594cf3dab58dd174bb29349511f61c Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 11:47:10 -0800 Subject: [PATCH 11/35] add propert highlighting and open notebook editor when replace active --- .../contrib/search/browser/searchModel.ts | 65 +++++++++++++++---- .../contrib/search/browser/searchView.ts | 22 +++++-- 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 383204afb6cd2..71abd5d923d78 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -268,6 +268,8 @@ export class FileMatch extends Disposable implements IFileMatch { private _modelDecorations: string[] = []; private _currentMatchCellDecorations: string[] = []; private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; + private _allMatchesDecorations: ICellModelDecorations[] = []; + private _allMatchesCellDecorations: string[] = []; private _context: Map = new Map(); public get context(): Map { @@ -339,16 +341,22 @@ export class FileMatch extends Disposable implements IFileMatch { } this._notebookUpdateScheduler.schedule(); }) ?? null; + this._notebookUpdateScheduler.schedule(); - this.updateHighlights(); console.log(`added widget ${this._notebookEditorWidget.textModel?.uri}`); } unbindEditorWidget(widget: NotebookEditorWidget) { - this.updateMatchesForModel(); + this.updateMatchesForEditorWidget(); if (this._notebookEditorWidget) { - this._updateScheduler.cancel(); - this._model = null; + this._notebookUpdateScheduler.cancel(); + this._notebookEditorWidget.changeModelDecorations((accessor) => { + this._currentMatchDecorations = { + kind: 'input', + decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []) + }; + }); + this._notebookEditorWidget = null; this._editorWidgetListener?.dispose(); } this._notebookEditorWidget = null; @@ -443,13 +451,8 @@ export class FileMatch extends Disposable implements IFileMatch { }); }); - // this.addContext( - // addContextToEditorMatches(textSearchResults, model, this.parent().parent().query!) - // .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) - // .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); - + this.setAllFindMatchesDecorations(matches); this._onChange.fire({ forceUpdateModel: modelChange }); - // this.updateHighlights(); } private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { @@ -607,9 +610,6 @@ export class FileMatch extends Disposable implements IFileMatch { public async showMatch(match: NotebookMatch) { const offset = await this.highlightCurrentFindMatchDecoration(match); - // if (this._notebookEditorWidget) { - // this._notebookEditorWidget.focusNotebookCell(match.cell, 'container', {}); - // } this.revealCellRange(match, offset); } @@ -679,6 +679,45 @@ export class FileMatch extends Disposable implements IFileMatch { } } + private setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { + if (!this._notebookEditorWidget) { + return; + } + this._notebookEditorWidget.changeModelDecorations((accessor) => { + + const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; + + const deltaDecorations: ICellModelDeltaDecorations[] = cellFindMatches.map(cellFindMatch => { + // Find matches + const newFindMatchesDecorations: IModelDeltaDecoration[] = new Array(cellFindMatch.length); + for (let i = 0; i < cellFindMatch.contentMatches.length; i++) { + newFindMatchesDecorations[i] = { + range: cellFindMatch.contentMatches[i].range, + options: findMatchesOptions + }; + } + + return { ownerId: cellFindMatch.cell.handle, decorations: newFindMatchesDecorations }; + }); + + this._allMatchesDecorations = accessor.deltaDecorations(this._allMatchesDecorations, deltaDecorations); + }); + + this._allMatchesCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._allMatchesCellDecorations, cellFindMatches.map(cellFindMatch => { + return { + ownerId: cellFindMatch.cell.handle, + handle: cellFindMatch.cell.handle, + options: { + overviewRuler: { + color: overviewRulerFindMatchForeground, + modelRanges: cellFindMatch.contentMatches.map(match => match.range), + includeOutput: cellFindMatch.webviewMatches.length > 0 + } + } + }; + })); + } + private clearCurrentFindMatchDecoration() { if (!this._notebookEditorWidget) { return; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 056c6160b76a6..ef02b15371bf4 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -81,6 +81,7 @@ import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/se import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchCompletionExitCode, SearchSortOrder, TextSearchCompleteMessageType, ViewMode } from 'vs/workbench/services/search/common/search'; import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/searchExtTypes'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; const $ = dom.$; @@ -186,6 +187,7 @@ export class SearchView extends ViewPane { @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, + @INotebookService private readonly notebookService: INotebookService, ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); @@ -1758,17 +1760,24 @@ export class SearchView extends ViewPane { this.currentSelectedFileMatch = undefined; } + private shouldOpenInNotebookEditor(uri: URI): boolean { + return this.notebookService.getContributedNotebookTypes(uri).length > 0; + } + private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; - return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(lineMatch instanceof NotebookMatch)) ? + + const resource = lineMatch instanceof Match ? lineMatch.parent().resource : (lineMatch).resource; + return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(this.shouldOpenInNotebookEditor(resource))) ? this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) : - this.open(lineMatch, preserveFocus, sideBySide, pinned); + this.open(lineMatch, preserveFocus, sideBySide, pinned, resource); } - async open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + async open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean, resourceInput?: URI): Promise { const selection = this.getSelectionFrom(element); - const resource = element instanceof Match ? element.parent().resource : (element).resource; - + const oldParentMatches = element instanceof Match ? element.parent().matches() : []; + const resource = resourceInput ?? (element instanceof Match ? element.parent().resource : (element).resource); + this.shouldOpenInNotebookEditor(resource); let editor: IEditorPane | undefined; try { editor = await this.editorService.openEditor({ @@ -1798,13 +1807,12 @@ export class SearchView extends ViewPane { if (editor instanceof NotebookEditor) { const controller = editor.getControl()?.getContribution(NotebookFindContrib.id); if (element instanceof Match) { - const matchIndex = element.parent().matches().findIndex(e => e.id() === element.id()); if (element instanceof NotebookMatch) { // no op for now! element.parent().showMatch(element); } else { - + const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); controller?.show(this.searchWidget.searchInput.getValue(), { matchIndex, focus: false }); } } From da22b6ac4727a7f335d8e7b562884d0a56c2a12c Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 12:19:43 -0800 Subject: [PATCH 12/35] cleanup and compile error fixing --- .../contrib/search/browser/replaceService.ts | 3 -- .../contrib/search/browser/searchModel.ts | 11 +++-- .../search/browser/searchNotebookHelpers.ts | 17 ------- .../services/search/browser/searchService.ts | 3 -- .../services/search/common/searchService.ts | 44 +------------------ 5 files changed, 9 insertions(+), 69 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 6c8137e21fb64..ede2f234da58c 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -123,9 +123,6 @@ export class ReplaceService implements IReplaceService { } }); - - // const notebookPromises = edits.notebookEdits.map(async e => this.editorService.save([...this.editorService.findEditors(e.resource)])); - return Promises.settled(rawTextPromises); } diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 71c18fc2129f2..a5fdb5d03a880 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -33,7 +33,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { minimapFindMatch, overviewRulerFindMatchForeground, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, NotebookOverviewRulerLane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; @@ -665,7 +665,8 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: overviewRulerSelectionHighlightForeground, modelRanges: [match.range()], - includeOutput: false + includeOutput: false, + position: NotebookOverviewRulerLane.Center } } } as INotebookDeltaDecoration]); @@ -683,7 +684,8 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: overviewRulerSelectionHighlightForeground, modelRanges: [], - includeOutput: true + includeOutput: true, + position: NotebookOverviewRulerLane.Center } } } as INotebookDeltaDecoration]); @@ -724,7 +726,8 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: overviewRulerFindMatchForeground, modelRanges: cellFindMatch.contentMatches.map(match => match.range), - includeOutput: cellFindMatch.webviewMatches.length > 0 + includeOutput: cellFindMatch.webviewMatches.length > 0, + position: NotebookOverviewRulerLane.Center } } }; diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index 9a52633ecd746..f02e122c820d4 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -94,20 +94,3 @@ export function notebookEditorMatchesToTextSearchResults(cellFindMatches: CellFi return notebookEditorMatchToTextSearchResult(sameLineMatches, previewOptions); }).filter((elem): elem is NotebookTextSearchMatch => !!elem); } - -// export function addContextToNotebookEditorMatches(cellFindMatches: CellFindMatchWithIndex[], query: ITextQuery): ITextSearchResult[] { -// const results: ITextSearchResult[] = []; - -// // we need a cell and all of its corresponding textmatches -// // ^ we need this split by webview and content matches - -// // -// cellFindMatches.forEach(findMatch => { -// findMatch.contentMatches.forEach(contentMatch => { -// const result = addContextToEditorMatches() -// }) -// }) - - -// return results; -// } diff --git a/src/vs/workbench/services/search/browser/searchService.ts b/src/vs/workbench/services/search/browser/searchService.ts index 0e90f9c4e529f..f1fc4a45c5b0d 100644 --- a/src/vs/workbench/services/search/browser/searchService.ts +++ b/src/vs/workbench/services/search/browser/searchService.ts @@ -27,8 +27,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; -// import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; - export class RemoteSearchService extends SearchService { constructor( @IModelService modelService: IModelService, @@ -39,7 +37,6 @@ export class RemoteSearchService extends SearchService { @IFileService fileService: IFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IUriIdentityService uriIdentityService: IUriIdentityService, - // @INotebookEditorService notebookEditorService: INotebookEditorService ) { super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService); const searchProvider = this.instantiationService.createInstance(LocalFileSearchWorkerClient); diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 74441e3cf08b4..63b1c192a8668 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -19,10 +19,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; - -// import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -// import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; - import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; @@ -48,7 +44,6 @@ export class SearchService extends Disposable implements ISearchService { @IExtensionService private readonly extensionService: IExtensionService, @IFileService private readonly fileService: IFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - // @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, ) { super(); } @@ -80,7 +75,7 @@ export class SearchService extends Disposable implements ISearchService { async textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void, notebookURIs?: ResourceSet): Promise { // Get local results from dirty/untitled - const localResults = await this.getLocalResults(query); + const localResults = this.getLocalResults(query); if (onProgress) { arrays.coalesce([...localResults.results.values()]).forEach(onProgress); @@ -409,7 +404,7 @@ export class SearchService extends Disposable implements ISearchService { } } - private async getLocalResults(query: ITextQuery): Promise<{ results: ResourceMap; limitHit: boolean }> { + private getLocalResults(query: ITextQuery): { results: ResourceMap; limitHit: boolean } { const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); let limitHit = false; @@ -478,41 +473,6 @@ export class SearchService extends Disposable implements ISearchService { localResults.set(originalResource, null); } }); - - - // const notebookWidgets = this.notebookEditorService.retrieveAllExistingWidgets(); - // for (const borrowWidget of notebookWidgets) { - // const widget = borrowWidget.value; - // if (!widget || !widget.viewModel) { - // continue; - // } - // const askMax = isNumber(query.maxResults) ? query.maxResults + 1 : Number.MAX_SAFE_INTEGER; - // let matches = await widget - // .find(query.contentPattern.pattern, { - // regex: query.contentPattern.isRegExp, - // wholeWord: query.contentPattern.isWordMatch, - // caseSensitive: query.contentPattern.isCaseSensitive, - // includeMarkupInput: false, - // includeMarkupPreview: true, - // includeCodeInput: true, - // includeOutput: true, - // }, CancellationToken.None); - - - // if (matches.length) { - // if (askMax && matches.length >= askMax) { - // limitHit = true; - // matches = matches.slice(0, askMax - 1); - // } - - // const fileMatch = new FileMatch(widget.viewModel.uri); - // localResults.set(widget.viewModel.uri, fileMatch); - - // fileMatch.results = notebookEditorMatchesToTextSearchResults(matches, query.previewOptions); - // } else { - // localResults.set(widget.viewModel.uri, null); - // } - // } } return { From 779e7210b8b93638e6e7a326a2f07f02652e056b Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 15:19:38 -0800 Subject: [PATCH 13/35] fix function for notebook opening in search --- src/vs/workbench/contrib/search/browser/searchView.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index d6474021c5ebb..5aab5ab9fbedd 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -21,6 +21,7 @@ import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; +import * as network from 'vs/base/common/network'; import 'vs/css!./media/searchview'; import { getCodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -1769,14 +1770,15 @@ export class SearchView extends ViewPane { } private shouldOpenInNotebookEditor(uri: URI): boolean { - return this.notebookService.getContributedNotebookTypes(uri).length > 0; + // + return uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0; } private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; const resource = lineMatch instanceof Match ? lineMatch.parent().resource : (lineMatch).resource; - return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(this.shouldOpenInNotebookEditor(resource))) ? + return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(lineMatch instanceof NotebookMatch) && !(this.shouldOpenInNotebookEditor(resource))) ? this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) : this.open(lineMatch, preserveFocus, sideBySide, pinned, resource); } From b8c51333f3de9ca938db78a4c2cb2bbfc3d46e4d Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 15:23:45 -0800 Subject: [PATCH 14/35] clarity on shouldOpenInNotebookEditor --- src/vs/workbench/contrib/search/browser/searchView.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 5aab5ab9fbedd..32446c432b7a9 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1769,16 +1769,17 @@ export class SearchView extends ViewPane { this.currentSelectedFileMatch = undefined; } - private shouldOpenInNotebookEditor(uri: URI): boolean { - // - return uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0; + private shouldOpenInNotebookEditor(match: Match, uri: URI): boolean { + // Untitled files will return a false positive for getContributedNotebookTypes. + // Since untitled files are already open, then untitled notebooks should return NotebookMatch results. + return match instanceof NotebookMatch || (uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0); } private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { const useReplacePreview = this.configurationService.getValue().search.useReplacePreview; const resource = lineMatch instanceof Match ? lineMatch.parent().resource : (lineMatch).resource; - return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(lineMatch instanceof NotebookMatch) && !(this.shouldOpenInNotebookEditor(resource))) ? + return (useReplacePreview && this.viewModel.isReplaceActive() && !!this.viewModel.replaceString && !(this.shouldOpenInNotebookEditor(lineMatch, resource))) ? this.replaceService.openReplacePreview(lineMatch, preserveFocus, sideBySide, pinned) : this.open(lineMatch, preserveFocus, sideBySide, pinned, resource); } @@ -1787,7 +1788,6 @@ export class SearchView extends ViewPane { const selection = this.getSelectionFrom(element); const oldParentMatches = element instanceof Match ? element.parent().matches() : []; const resource = resourceInput ?? (element instanceof Match ? element.parent().resource : (element).resource); - this.shouldOpenInNotebookEditor(resource); let editor: IEditorPane | undefined; try { editor = await this.editorService.openEditor({ From 15ea8bca9054491a1fc6c83392c6af8b62d883ea Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 16:30:32 -0800 Subject: [PATCH 15/35] added experimental flag --- .../contrib/search/browser/search.contribution.ts | 5 +++++ .../workbench/contrib/search/browser/searchModel.ts | 13 ++++++++++--- src/vs/workbench/services/search/common/search.ts | 3 +++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3cfb478d33cc3..652eb8009207c 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -350,6 +350,11 @@ configurationRegistry.registerConfiguration({ ], 'description': nls.localize('search.defaultViewMode', "Controls the default search result view mode.") }, + 'search.experimental.notebookSearch': { + type: 'boolean', + description: nls.localize('search.experimental.notebookSearch', "Controls whether to use the experimental notebook search in the global search."), + default: false + }, } }); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index a5fdb5d03a880..63a39ab753668 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -34,13 +34,11 @@ import { minimapFindMatch, overviewRulerFindMatchForeground, overviewRulerSelect import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, NotebookOverviewRulerLane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -// import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { notebookEditorMatchesToTextSearchResults, NotebookMatchInfo, NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; -// import { addContextToNotebookEditorMatches, notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, QueryType, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; @@ -1420,6 +1418,7 @@ export class SearchResult extends Disposable { @IModelService private readonly modelService: IModelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); @@ -1531,6 +1530,12 @@ export class SearchResult extends Disposable { } private onDidAddNotebookEditorWidget(widget: NotebookEditorWidget): void { + const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; + + if (!experimentalNotebooksEnabled) { + return; + } + widget.onWillChangeModel( (model) => { if (model) { @@ -1895,7 +1900,9 @@ export class SearchModel extends Disposable { onProgress?.(p); }; - const notebookResult = await this.notebookSearch(query, onProgressCall); + const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; + + const notebookResult = experimentalNotebooksEnabled ? await this.notebookSearch(query, onProgressCall) : { messages: [], results: [] }; const currentResult = await this.searchService.textSearch( searchQuery, this.currentCancelTokenSource.token, onProgressCall, diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 27f3b8a1a75ee..d3520c993907d 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -400,6 +400,9 @@ export interface ISearchConfigurationProperties { badges: boolean; }; defaultViewMode: ViewMode; + experimental: { + notebookSearch: boolean; + }; } export interface ISearchConfiguration extends IFilesConfiguration { From 310c13a80ebd44f732c8f74cc980238b60567326 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 19 Jan 2023 17:05:44 -0800 Subject: [PATCH 16/35] extra docs for notebook extractSelectionLine --- .../browser/view/renderers/webviewPreloads.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index ddbb00f2f7172..6a7a7d2014b28 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -950,23 +950,27 @@ async function webviewPreloads(ctx: PreloadContext) { } function extractSelectionLine(selection: Selection): ISearchPreviewInfo { - const range = selection.getRangeAt(0); + + // we need to keep a reference to the old selection range to re-apply later const oldRange = range.cloneRange(); const captureLength = selection.toString().length; - // selection.collapseToStart(); - // oldRange.setStart(range.startContainer, range.startOffset); - // oldRange.setEnd(range.endContainer, range.endOffset); + // use selection API to modify selection to get entire line (the first line if multi-select) + + // collapse selection to start so that the cursor position is at beginning of match + selection.collapseToStart(); + + // extend selection in both directions to select the line selection.modify('move', 'backward', 'lineboundary'); selection.modify('extend', 'forward', 'lineboundary'); - const newRange = selection.getRangeAt(0); const line = selection.toString(); - const rangeStart = getStartOffset(newRange, oldRange); - // const rangeStart = oldRange.startOffset - newRange.startOffset; - // const rangeEnd = oldRange.endOffset - newRange.startOffset; - // console.log(getStartOffset(newRange, oldRange)); + + // using the original range and the new range, we can find the offset of the match from the line start. + const rangeStart = getStartOffset(selection.getRangeAt(0), oldRange); + + // line range for match const lineRange = { start: rangeStart, end: rangeStart + captureLength, @@ -980,6 +984,8 @@ async function webviewPreloads(ctx: PreloadContext) { } function getStartOffset(lineRange: Range, originalRange: Range) { + // sometimes, the old and new range are in different DOM elements (ie: when the match is inside of ) + // so we need to find the first common ancestor DOM element and find the positions of the old and new range relative to that. const firstCommonAncestor = findFirstCommonAncestor(lineRange.startContainer, originalRange.startContainer); const selectionOffset = getSelectionOffsetRelativeTo(firstCommonAncestor, lineRange.startContainer) + lineRange.startOffset; @@ -1006,8 +1012,9 @@ async function webviewPreloads(ctx: PreloadContext) { return offset; } - let prevSibling = currentNode.previousSibling; + // count the number of chars before the current dom elem and the start of the dom + let prevSibling = currentNode.previousSibling; while (prevSibling) { const nodeContent = prevSibling.nodeValue || ''; offset += nodeContent.length; From a7827473c9d9fb5c64d01cc0a07412e493c52c27 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 20 Jan 2023 13:07:40 -0800 Subject: [PATCH 17/35] fix experimental flag bug --- src/vs/workbench/contrib/search/browser/searchModel.ts | 4 +++- src/vs/workbench/contrib/search/browser/searchView.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 63a39ab753668..a183c84e3c56b 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -298,6 +298,7 @@ export class FileMatch extends Disposable implements IFileMatch { @IReplaceService private readonly replaceService: IReplaceService, @ILabelService readonly labelService: ILabelService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); this._resource = this.rawMatch.resource; @@ -315,7 +316,8 @@ export class FileMatch extends Disposable implements IFileMatch { private async createMatches(): Promise { const model = this.modelService.getModel(this._resource); - const notebookEditorWidgetBorrow = this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource); + const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; + const notebookEditorWidgetBorrow = experimentalNotebooksEnabled ? this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource) : undefined; if (notebookEditorWidgetBorrow?.value) { this.bindEditorWidget(notebookEditorWidgetBorrow.value); await this.updateMatchesForEditorWidget(); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 32446c432b7a9..b1b0665e2a405 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1772,6 +1772,8 @@ export class SearchView extends ViewPane { private shouldOpenInNotebookEditor(match: Match, uri: URI): boolean { // Untitled files will return a false positive for getContributedNotebookTypes. // Since untitled files are already open, then untitled notebooks should return NotebookMatch results. + + // notebookMatch are only created when search.experimental.notebookSearch is enabled, so this should never return true if experimental flag is disabled. return match instanceof NotebookMatch || (uri.scheme !== network.Schemas.untitled && this.notebookService.getContributedNotebookTypes(uri).length > 0); } @@ -1818,8 +1820,6 @@ export class SearchView extends ViewPane { const controller = editor.getControl()?.getContribution(NotebookFindContrib.id); if (element instanceof Match) { if (element instanceof NotebookMatch) { - - // no op for now! element.parent().showMatch(element); } else { const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); From eff7d265127a6620881f0a432182adf4992eaaad Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 20 Jan 2023 16:53:44 -0800 Subject: [PATCH 18/35] incorporated shared findMatchDecorationModel --- .../contrib/find/findMatchDecorationModel.ts | 131 +++++++----- .../browser/contrib/find/findModel.ts | 11 +- .../contrib/search/browser/searchModel.ts | 190 +++--------------- .../contrib/search/browser/searchView.ts | 12 +- 4 files changed, 125 insertions(+), 219 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 2f407b50de5d3..4669eddb4e4bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { FindMatch, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { FindDecorations } from 'vs/editor/contrib/find/browser/findDecorations'; +import { Range } from 'vs/editor/common/core/range'; import { overviewRulerSelectionHighlightForeground, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; -import { CellFindMatchWithIndex, CellWebviewFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookDeltaDecoration, INotebookEditor, NotebookOverviewRulerLane, } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, ICellViewModel, INotebookDeltaDecoration, INotebookEditor, NotebookOverviewRulerLane, } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export class FindMatchDecorationModel extends Disposable { private _allMatchesDecorations: ICellModelDecorations[] = []; @@ -16,7 +17,7 @@ export class FindMatchDecorationModel extends Disposable { private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; constructor( - private readonly _notebookEditor: INotebookEditor + private _notebookEditor: INotebookEditor | undefined, ) { super(); } @@ -25,68 +26,91 @@ export class FindMatchDecorationModel extends Disposable { return this._currentMatchDecorations; } - public async highlightCurrentFindMatchDecoration(cell: ICellViewModel, match: FindMatch | CellWebviewFindMatch): Promise { + set notebookEditor(notebookEditor: INotebookEditor | undefined) { + if (this._notebookEditor) { + // clear any previous decorations if applicable + this.clearDecorations(); + } + this._notebookEditor = notebookEditor; + } - if (match instanceof FindMatch) { - this.clearCurrentFindMatchDecoration(); + public clearDecorations() { + this.clearCurrentFindMatchDecoration(); + this.setAllFindMatchesDecorations([]); + } - // match is an editor FindMatch, we update find match decoration in the editor - // we will highlight the match in the webview - this._notebookEditor.changeModelDecorations(accessor => { - const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION; - - const decorations: IModelDeltaDecoration[] = [ - { range: match.range, options: findMatchesOptions } - ]; - const deltaDecoration: ICellModelDeltaDecorations = { - ownerId: cell.handle, - decorations: decorations - }; - - this._currentMatchDecorations = { - kind: 'input', - decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], [deltaDecoration]) - }; - }); - - this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ - ownerId: cell.handle, - handle: cell.handle, - options: { - overviewRuler: { - color: overviewRulerSelectionHighlightForeground, - modelRanges: [match.range], - includeOutput: false, - position: NotebookOverviewRulerLane.Center - } - } - } as INotebookDeltaDecoration]); + public async highlightCurrentFindMatchDecorationInCell(cell: ICellViewModel, cellRange: Range): Promise { + if (!this._notebookEditor) { return null; - } else { - this.clearCurrentFindMatchDecoration(); + } + + this.clearCurrentFindMatchDecoration(); - const offset = await this._notebookEditor.highlightFind(cell, match.index); - this._currentMatchDecorations = { kind: 'output', index: match.index }; + // match is an editor FindMatch, we update find match decoration in the editor + // we will highlight the match in the webview + this._notebookEditor.changeModelDecorations(accessor => { + const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION; - this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ + const decorations: IModelDeltaDecoration[] = [ + { range: cellRange, options: findMatchesOptions } + ]; + const deltaDecoration: ICellModelDeltaDecorations = { ownerId: cell.handle, - handle: cell.handle, - options: { - overviewRuler: { - color: overviewRulerSelectionHighlightForeground, - modelRanges: [], - includeOutput: true, - position: NotebookOverviewRulerLane.Center - } + decorations: decorations + }; + + this._currentMatchDecorations = { + kind: 'input', + decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], [deltaDecoration]) + }; + }); + + this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ + ownerId: cell.handle, + handle: cell.handle, + options: { + overviewRuler: { + color: overviewRulerSelectionHighlightForeground, + modelRanges: [cellRange], + includeOutput: false, + position: NotebookOverviewRulerLane.Center } - } as INotebookDeltaDecoration]); + } + } as INotebookDeltaDecoration]); - return offset; + return null; + } + + public async highlightCurrentFindMatchDecorationInWebview(cell: ICellViewModel, index: number): Promise { + if (!this._notebookEditor) { + return null; } + this.clearCurrentFindMatchDecoration(); + + const offset = await this._notebookEditor.highlightFind(index); + this._currentMatchDecorations = { kind: 'output', index: index }; + + this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, [{ + ownerId: cell.handle, + handle: cell.handle, + options: { + overviewRuler: { + color: overviewRulerSelectionHighlightForeground, + modelRanges: [], + includeOutput: true, + position: NotebookOverviewRulerLane.Center + } + } + } as INotebookDeltaDecoration]); + + return offset; } public clearCurrentFindMatchDecoration() { + if (!this._notebookEditor) { + return; + } if (this._currentMatchDecorations?.kind === 'input') { this._notebookEditor.changeModelDecorations(accessor => { accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); @@ -101,6 +125,9 @@ export class FindMatchDecorationModel extends Disposable { public setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { + if (!this._notebookEditor) { + return; + } this._notebookEditor.changeModelDecorations((accessor) => { const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 6848616e61e28..7341092794ab5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -450,11 +450,12 @@ export class FindModel extends Disposable { private async highlightCurrentFindMatchDecoration(cellIndex: number, matchIndex: number): Promise { const cell = this._findMatches[cellIndex].cell; const match = this._findMatches[cellIndex].getMatch(matchIndex); - return this._findMatchDecorationModel.highlightCurrentFindMatchDecoration(cell, - (matchIndex < this._findMatches[cellIndex].contentMatches.length) ? - (match as FindMatch) : - (match as CellWebviewFindMatch) - ); + + if (matchIndex < this._findMatches[cellIndex].contentMatches.length) { + return this._findMatchDecorationModel.highlightCurrentFindMatchDecorationInCell(cell, (match as FindMatch).range); + } else { + return this._findMatchDecorationModel.highlightCurrentFindMatchDecorationInWebview(cell, (match as CellWebviewFindMatch).index); + } } clear() { diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index a183c84e3c56b..2b8dbd174c494 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -22,7 +22,6 @@ import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/model'; -import { FindDecorations } from 'vs/editor/contrib/find/browser/findDecorations'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStatWithPartialMetadata } from 'vs/platform/files/common/files'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -30,10 +29,11 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { minimapFindMatch, overviewRulerFindMatchForeground, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; +import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { CellFindMatchWithIndex, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, NotebookOverviewRulerLane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { FindMatchDecorationModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel'; +import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -277,10 +277,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _notebookUpdateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; - private _currentMatchCellDecorations: string[] = []; - private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; - private _allMatchesDecorations: ICellModelDecorations[] = []; - private _allMatchesCellDecorations: string[] = []; + private _findMatchDecorationModel: FindMatchDecorationModel; private _context: Map = new Map(); public get context(): Map { @@ -307,6 +304,7 @@ export class FileMatch extends Disposable implements IFileMatch { this._updateScheduler = new RunOnceScheduler(this.updateMatchesForModel.bind(this), 250); this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); this._name = new Lazy(() => labelService.getUriBasenameLabel(this.resource)); + this._findMatchDecorationModel = new FindMatchDecorationModel(undefined); this.createMatches(); } @@ -345,9 +343,26 @@ export class FileMatch extends Disposable implements IFileMatch { this.updateHighlights(); } + private onModelWillDispose(): void { + // Update matches because model might have some dirty changes + this.updateMatchesForModel(); + this.unbindModel(); + } + + private unbindModel(): void { + if (this._model) { + this._updateScheduler.cancel(); + this._model.changeDecorations((accessor) => { + this._modelDecorations = accessor.deltaDecorations(this._modelDecorations, []); + }); + this._model = null; + this._modelListener!.dispose(); + } + } + bindEditorWidget(widget: NotebookEditorWidget) { this._notebookEditorWidget = widget; - + this._findMatchDecorationModel.notebookEditor = widget; this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { if (!e.rawEvents.some(event => event.kind === NotebookCellsChangeType.ChangeCellContent || event.kind === NotebookCellsChangeType.ModelChange)) { return; @@ -355,42 +370,16 @@ export class FileMatch extends Disposable implements IFileMatch { this._notebookUpdateScheduler.schedule(); }) ?? null; this._notebookUpdateScheduler.schedule(); - - console.log(`added widget ${this._notebookEditorWidget.textModel?.uri}`); } - unbindEditorWidget(widget: NotebookEditorWidget) { + unbindEditorWidget() { this.updateMatchesForEditorWidget(); if (this._notebookEditorWidget) { this._notebookUpdateScheduler.cancel(); - this._notebookEditorWidget.changeModelDecorations((accessor) => { - this._currentMatchDecorations = { - kind: 'input', - decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []) - }; - }); - this._notebookEditorWidget = null; + this._findMatchDecorationModel.notebookEditor = undefined; this._editorWidgetListener?.dispose(); } this._notebookEditorWidget = null; - console.log(`removed widget ${widget.textModel?.uri}`); - } - - private onModelWillDispose(): void { - // Update matches because model might have some dirty changes - this.updateMatchesForModel(); - this.unbindModel(); - } - - private unbindModel(): void { - if (this._model) { - this._updateScheduler.cancel(); - this._model.changeDecorations((accessor) => { - this._modelDecorations = accessor.deltaDecorations(this._modelDecorations, []); - }); - this._model = null; - this._modelListener!.dispose(); - } } private updateMatchesForModel(): void { @@ -464,7 +453,7 @@ export class FileMatch extends Disposable implements IFileMatch { }); }); - this.setAllFindMatchesDecorations(matches); + this._findMatchDecorationModel.setAllFindMatchesDecorations(matches); this._onChange.fire({ forceUpdateModel: modelChange }); } @@ -617,6 +606,8 @@ export class FileMatch extends Disposable implements IFileMatch { override dispose(): void { this.setSelectedMatch(null); this.unbindModel(); + this.unbindEditorWidget(); + this._findMatchDecorationModel.dispose(); this._onDispose.fire(); super.dispose(); } @@ -627,140 +618,21 @@ export class FileMatch extends Disposable implements IFileMatch { } private async highlightCurrentFindMatchDecoration(match: NotebookMatch): Promise { - if (!this._notebookEditorWidget) { - return Promise.resolve(null); - } - // needs - // notebook cell - // only cellmatch: - // match range - // only webviewmatch: - // - if (match.webviewIndex === undefined) { - this.clearCurrentFindMatchDecoration(); - // match is an editor FindMatch, we update find match decoration in the editor - // we will highlight the match in the webview - this._notebookEditorWidget.changeModelDecorations(accessor => { - const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION; - - const decorations: IModelDeltaDecoration[] = [ - { range: match.range(), options: findMatchesOptions } - ]; - const deltaDecoration: ICellModelDeltaDecorations = { - ownerId: match.cell.handle, - decorations: decorations - }; - - this._currentMatchDecorations = { - kind: 'input', - decorations: accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], [deltaDecoration]) - }; - }); - - this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, [{ - ownerId: match.cell.handle, - handle: match.cell.handle, - options: { - overviewRuler: { - color: overviewRulerSelectionHighlightForeground, - modelRanges: [match.range()], - includeOutput: false, - position: NotebookOverviewRulerLane.Center - } - } - } as INotebookDeltaDecoration]); - - return null; + return this._findMatchDecorationModel.highlightCurrentFindMatchDecorationInCell(match.cell, match.range()); } else { - this.clearCurrentFindMatchDecoration(); - const offset = await this._notebookEditorWidget.highlightFind(match.webviewIndex); - this._currentMatchDecorations = { kind: 'output', index: match.webviewIndex }; - - this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, [{ - ownerId: match.cell.handle, - handle: match.cell.handle, - options: { - overviewRuler: { - color: overviewRulerSelectionHighlightForeground, - modelRanges: [], - includeOutput: true, - position: NotebookOverviewRulerLane.Center - } - } - } as INotebookDeltaDecoration]); - - return offset; + return this._findMatchDecorationModel.highlightCurrentFindMatchDecorationInWebview(match.cell, match.webviewIndex); } } - private setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { - if (!this._notebookEditorWidget) { - return; - } - this._notebookEditorWidget.changeModelDecorations((accessor) => { - - const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; - - const deltaDecorations: ICellModelDeltaDecorations[] = cellFindMatches.map(cellFindMatch => { - // Find matches - const newFindMatchesDecorations: IModelDeltaDecoration[] = new Array(cellFindMatch.length); - for (let i = 0; i < cellFindMatch.contentMatches.length; i++) { - newFindMatchesDecorations[i] = { - range: cellFindMatch.contentMatches[i].range, - options: findMatchesOptions - }; - } - - return { ownerId: cellFindMatch.cell.handle, decorations: newFindMatchesDecorations }; - }); - - this._allMatchesDecorations = accessor.deltaDecorations(this._allMatchesDecorations, deltaDecorations); - }); - - this._allMatchesCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._allMatchesCellDecorations, cellFindMatches.map(cellFindMatch => { - return { - ownerId: cellFindMatch.cell.handle, - handle: cellFindMatch.cell.handle, - options: { - overviewRuler: { - color: overviewRulerFindMatchForeground, - modelRanges: cellFindMatch.contentMatches.map(match => match.range), - includeOutput: cellFindMatch.webviewMatches.length > 0, - position: NotebookOverviewRulerLane.Center - } - } - }; - })); - } - - private clearCurrentFindMatchDecoration() { - if (!this._notebookEditorWidget) { - return; - } - - if (this._currentMatchDecorations?.kind === 'input') { - this._notebookEditorWidget.changeModelDecorations(accessor => { - accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); - this._currentMatchDecorations = null; - }); - } else if (this._currentMatchDecorations?.kind === 'output') { - this._notebookEditorWidget.unHighlightFind(this._currentMatchDecorations.index); - } - - this._currentMatchCellDecorations = this._notebookEditorWidget.deltaCellDecorations(this._currentMatchCellDecorations, []); - } - private revealCellRange(match: NotebookMatch, outputOffset: number | null) { if (!this._notebookEditorWidget) { return; } if (match.webviewIndex) { - // reveal output range this._notebookEditorWidget.focusElement(match.cell); const index = this._notebookEditorWidget.getCellIndex(match.cell); if (index !== undefined) { - // const range: ICellRange = { start: index, end: index + 1 }; this._notebookEditorWidget.revealCellOffsetInCenterAsync(match.cell, outputOffset ?? 0); } } else { @@ -884,7 +756,7 @@ export class FolderMatch extends Disposable { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - fileMatch.unbindEditorWidget(editor); + fileMatch.unbindEditorWidget(); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index b1b0665e2a405..b9abfca7fe63a 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -61,7 +61,6 @@ 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 { NotebookFindContrib } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; import { appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActionsBase'; @@ -1817,13 +1816,20 @@ export class SearchView extends ViewPane { } if (editor instanceof NotebookEditor) { - const controller = editor.getControl()?.getContribution(NotebookFindContrib.id); if (element instanceof Match) { if (element instanceof NotebookMatch) { element.parent().showMatch(element); } else { + // problems with this: + // 1: relies on the notebook having already been binded to the fileMatch (race condition) + // 2: doesn't wait for output to load before revealing cell, so when everything is loaded, there might be an offset const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); - controller?.show(this.searchWidget.searchInput.getValue(), { matchIndex, focus: false }); + const sortedMatches = element.parent().matches().sort(searchMatchComparer); + const match = matchIndex >= sortedMatches.length ? sortedMatches[sortedMatches.length - 1] : sortedMatches[matchIndex]; + + if (match instanceof NotebookMatch) { + element.parent().showMatch(match); + } } } From b1e69a1283c15de699ba0158bbfb80a454e6889e Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 23 Jan 2023 12:09:15 -0800 Subject: [PATCH 19/35] jump to nth result on raw text results to notebook results --- .../contrib/search/browser/searchModel.ts | 30 ++++++++++++------- .../contrib/search/browser/searchView.ts | 26 ++++++++++------ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 2b8dbd174c494..6b67c369419d5 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -317,8 +317,7 @@ export class FileMatch extends Disposable implements IFileMatch { const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; const notebookEditorWidgetBorrow = experimentalNotebooksEnabled ? this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource) : undefined; if (notebookEditorWidgetBorrow?.value) { - this.bindEditorWidget(notebookEditorWidgetBorrow.value); - await this.updateMatchesForEditorWidget(); + await this.bindEditorWidget(notebookEditorWidgetBorrow.value); } else if (model) { this.bindModel(model); this.updateMatchesForModel(); @@ -360,7 +359,12 @@ export class FileMatch extends Disposable implements IFileMatch { } } - bindEditorWidget(widget: NotebookEditorWidget) { + async bindEditorWidget(widget: NotebookEditorWidget) { + + if (this._notebookEditorWidget === widget) { + return; + } + this._notebookEditorWidget = widget; this._findMatchDecorationModel.notebookEditor = widget; this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { @@ -369,10 +373,14 @@ export class FileMatch extends Disposable implements IFileMatch { } this._notebookUpdateScheduler.schedule(); }) ?? null; - this._notebookUpdateScheduler.schedule(); + await this.updateMatchesForEditorWidget(); } - unbindEditorWidget() { + unbindEditorWidget(widget?: NotebookEditorWidget) { + if (widget && this._notebookEditorWidget !== widget) { + return; + } + this.updateMatchesForEditorWidget(); if (this._notebookEditorWidget) { this._notebookUpdateScheduler.cancel(); @@ -739,15 +747,15 @@ export class FolderMatch extends Disposable { } } - bindEditorWidget(editor: NotebookEditorWidget, resource: URI) { + async bindEditorWidget(editor: NotebookEditorWidget, resource: URI) { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - fileMatch.bindEditorWidget(editor); + await fileMatch.bindEditorWidget(editor); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { - elem.bindEditorWidget(editor, resource); + await elem.bindEditorWidget(editor, resource); } } } @@ -756,7 +764,7 @@ export class FolderMatch extends Disposable { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - fileMatch.unbindEditorWidget(); + fileMatch.unbindEditorWidget(editor); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { @@ -1433,9 +1441,9 @@ export class SearchResult extends Disposable { folderMatch?.bindModel(model); } - private onNotebookEditorWidgetAdded(editor: NotebookEditorWidget, resource: URI): void { + private async onNotebookEditorWidgetAdded(editor: NotebookEditorWidget, resource: URI): Promise { const folderMatch = this._folderMatchesMap.findSubstr(resource); - folderMatch?.bindEditorWidget(editor, resource); + await folderMatch?.bindEditorWidget(editor, resource); } private onNotebookEditorWidgetRemoved(editor: NotebookEditorWidget, resource: URI): void { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index b9abfca7fe63a..13634c62ba79e 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1820,16 +1820,24 @@ export class SearchView extends ViewPane { if (element instanceof NotebookMatch) { element.parent().showMatch(element); } else { - // problems with this: - // 1: relies on the notebook having already been binded to the fileMatch (race condition) - // 2: doesn't wait for output to load before revealing cell, so when everything is loaded, there might be an offset - const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); - const sortedMatches = element.parent().matches().sort(searchMatchComparer); - const match = matchIndex >= sortedMatches.length ? sortedMatches[sortedMatches.length - 1] : sortedMatches[matchIndex]; - - if (match instanceof NotebookMatch) { - element.parent().showMatch(match); + const editorWidget = editor.getControl(); + if (editorWidget) { + // Ensure that the editor widget is binded. If if is, then this should return immediately. + // Otherwise, it will bind the widget. + await element.parent().bindEditorWidget(editorWidget); + + const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); + const matches = element.parent().matches(); + const match = matchIndex >= matches.length ? matches[matches.length - 1] : matches[matchIndex]; + + if (match instanceof NotebookMatch) { + element.parent().showMatch(match); + } + this.tree.setSelection([match], getSelectionKeyboardEvent()); + // todo: bug sometimes where results disappear + // todo: focus goes automatically to notebook when using arrow keys } + } } From 7b973328aafd460df29114d351181683cfa8c339 Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 7 Feb 2023 14:53:34 -0800 Subject: [PATCH 20/35] fix bug with focus shift --- src/vs/workbench/contrib/search/browser/searchModel.ts | 5 ++--- src/vs/workbench/contrib/search/browser/searchView.ts | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 09370ea8789c0..df86d3cfebb4f 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -33,7 +33,7 @@ import { minimapFindMatch, overviewRulerFindMatchForeground } from 'vs/platform/ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { FindMatchDecorationModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel'; -import { CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatchWithIndex } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -638,13 +638,12 @@ export class FileMatch extends Disposable implements IFileMatch { return; } if (match.webviewIndex) { - this._notebookEditorWidget.focusElement(match.cell); const index = this._notebookEditorWidget.getCellIndex(match.cell); if (index !== undefined) { this._notebookEditorWidget.revealCellOffsetInCenterAsync(match.cell, outputOffset ?? 0); } } else { - this._notebookEditorWidget.focusNotebookCell(match.cell, 'editor'); + match.cell.updateEditState(CellEditState.Editing, 'focusNotebookCell'); this._notebookEditorWidget.setCellEditorSelection(match.cell, match.range()); this._notebookEditorWidget.revealRangeInCenterIfOutsideViewportAsync(match.cell, match.range()); } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 13634c62ba79e..65770caea73b3 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1833,9 +1833,10 @@ export class SearchView extends ViewPane { if (match instanceof NotebookMatch) { element.parent().showMatch(match); } - this.tree.setSelection([match], getSelectionKeyboardEvent()); - // todo: bug sometimes where results disappear - // todo: focus goes automatically to notebook when using arrow keys + + if (!this.tree.getFocus().includes(match) || !this.tree.getSelection().includes(match)) { + this.tree.setSelection([match], getSelectionKeyboardEvent()); + } } } From 349b100fb66797133a04b2c2edef36c044b33b8d Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 7 Feb 2023 15:08:18 -0800 Subject: [PATCH 21/35] cleanup --- .../notebook/browser/services/notebookEditorService.ts | 1 + src/vs/workbench/contrib/search/browser/replaceService.ts | 1 - src/vs/workbench/contrib/search/browser/searchModel.ts | 6 +----- src/vs/workbench/contrib/search/browser/searchView.ts | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index 97bd92d99856b..92c3b01e76d4f 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -22,6 +22,7 @@ export interface INotebookEditorService { _serviceBrand: undefined; retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions, dimension?: Dimension): IBorrowValue; + retrieveExistingWidgetFromURI(resource: URI): IBorrowValue | undefined; retrieveAllExistingWidgets(): IBorrowValue[]; onDidAddNotebookEditor: Event; diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index ede2f234da58c..242dcff1b5ac3 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -220,7 +220,6 @@ export class ReplaceService implements IReplaceService { private createEdit(match: Match, text: string, resource: URI | null = null): ResourceTextEdit { const fileMatch: FileMatch = match.parent(); - return new ResourceTextEdit( resource ?? fileMatch.resource, { range: match.range(), text }, undefined, undefined diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index df86d3cfebb4f..3a938912a8d05 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -515,9 +515,7 @@ export class FileMatch extends Disposable implements IFileMatch { } matches(): Match[] { - const vals = this._matches.values(); - const arr = Array.from(vals); - return arr; + return Array.from(this._matches.values()); } remove(matches: Match | Match[]): void { @@ -1769,7 +1767,6 @@ export class SearchModel extends Disposable { }; } - private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, onProgress?: (result: ISearchProgressItem) => void) { const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const onProgressCall = (p: ISearchProgressItem) => { @@ -1788,7 +1785,6 @@ export class SearchModel extends Disposable { ); tokenSource.dispose(); return { ...currentResult, ...notebookResult }; - } async search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 65770caea73b3..a31d8c5fbefe1 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -553,7 +553,6 @@ export class SearchView extends ViewPane { } refreshTree(event?: IChangeEvent): void { - // animation frame and debounce const collapseResults = this.searchConfig.collapseResults; if (!event || event.added || event.removed) { // Refresh whole tree From d11d1692abf90959280a34d787847be5215517da Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 8 Feb 2023 12:48:41 -0800 Subject: [PATCH 22/35] fix tests and cleanup --- .../browser/view/renderers/webviewPreloads.ts | 1 - .../contrib/search/browser/searchModel.ts | 2 +- .../search/test/browser/searchActions.test.ts | 14 +++++++++- .../search/test/browser/searchModel.test.ts | 26 +++++++++++++------ .../search/test/browser/searchResult.test.ts | 14 +++++++++- .../search/test/browser/searchViewlet.test.ts | 17 +++++++++++- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 6a7a7d2014b28..965e10973258e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -923,7 +923,6 @@ async function webviewPreloads(ctx: PreloadContext) { if (match) { let offset = 0; try { - console.log(match.id); const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 3a938912a8d05..c3d355ef8af49 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1767,7 +1767,7 @@ export class SearchModel extends Disposable { }; } - private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, onProgress?: (result: ISearchProgressItem) => void) { + private async doSearch(query: ITextQuery, progressEmitter: Emitter, searchQuery: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const onProgressCall = (p: ISearchProgressItem) => { progressEmitter.fire(); diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index ca2031f95908d..f27540fe7c831 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -21,6 +21,10 @@ import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSea import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Search Actions', () => { @@ -30,6 +34,7 @@ suite('Search Actions', () => { setup(() => { instantiationService = new TestInstantiationService(); instantiationService.stub(IModelService, stubModelService(instantiationService)); + instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); instantiationService.stub(IKeybindingService, {}); instantiationService.stub(ILabelService, { getUriBasenameLabel: (uri: URI) => '' }); instantiationService.stub(IKeybindingService, 'resolveKeybinding', (keybinding: Keybinding) => USLayoutResolvedKeybinding.resolveKeybinding(keybinding, OS)); @@ -169,8 +174,15 @@ suite('Search Actions', () => { } function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + instantiationService.stub(IConfigurationService, config); return instantiationService.createInstance(ModelService); } + + function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + return instantiationService.createInstance(NotebookEditorWidgetService); + } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 697e1d4a1eb56..2cb9123f044a6 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -25,6 +25,10 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { isWindows } from 'vs/base/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; const nullEvent = new class { id: number = -1; @@ -75,14 +79,14 @@ suite('SearchModel', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILabelService, { getUriBasenameLabel: (uri: URI) => '' }); instantiationService.stub(IModelService, stubModelService(instantiationService)); + instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); instantiationService.stub(ISearchService, {}); instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); instantiationService.stub(ILogService, new NullLogService()); - - const config = new TestConfigurationService(); - config.setUserConfiguration('search', { searchOnType: true }); - instantiationService.stub(IConfigurationService, config); + // const config = new TestConfigurationService(); + // config.setUserConfiguration('search', { searchOnType: true }); + // instantiationService.stub(IConfigurationService, config); }); teardown(() => { @@ -171,9 +175,8 @@ suite('SearchModel', () => { await testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }); assert.ok(target.calledThrice); - const data = target.args[2]; - data[1].duration = -1; - assert.deepStrictEqual(['searchResultsFirstRender', { duration: -1 }], data); + assert.ok(target.calledWith('searchResultsFirstRender')); + assert.ok(target.calledWith('searchResultsFinished')); }); test('Search Model: Search reports timed telemetry on search when progress is not called', () => { @@ -355,9 +358,16 @@ suite('SearchModel', () => { } function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + instantiationService.stub(IConfigurationService, config); return instantiationService.createInstance(ModelService); } + function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + return instantiationService.createInstance(NotebookEditorWidgetService); + } + }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 54679cf406870..4b81f2a8651aa 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -25,6 +25,10 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { isWindows } from 'vs/base/common/platform'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -36,6 +40,7 @@ suite('SearchResult', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModelService, stubModelService(instantiationService)); + instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); instantiationService.stubPromise(IReplaceService, {}); instantiationService.stub(IReplaceService, 'replace', () => Promise.resolve(null)); @@ -516,11 +521,18 @@ suite('SearchResult', () => { } function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + instantiationService.stub(IConfigurationService, config); return instantiationService.createInstance(ModelService); } + function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + return instantiationService.createInstance(NotebookEditorWidgetService); + } + function getPopulatedSearchResult() { const testObject = aSearchResult(); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index f9471b65e6d71..1da5e284d825e 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -25,6 +25,10 @@ import { FileMatch, FolderMatch, Match, searchComparer, searchMatchComparer, Sea import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -33,6 +37,8 @@ suite('Search - Viewlet', () => { instantiation = new TestInstantiationService(); instantiation.stub(ILanguageConfigurationService, TestLanguageConfigurationService); instantiation.stub(IModelService, stubModelService(instantiation)); + instantiation.stub(INotebookEditorService, stubNotebookEditorService(instantiation)); + instantiation.set(IWorkspaceContextService, new TestContextService(TestWorkspace)); instantiation.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); instantiation.stub(ILabelService, new MockLabelService()); @@ -194,11 +200,20 @@ suite('Search - Viewlet', () => { } function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); + + const config = new TestConfigurationService(); + config.setUserConfiguration('search', { searchOnType: true, experimental: { notebookSearch: false } }); + instantiationService.stub(IConfigurationService, config); + return instantiationService.createInstance(ModelService); } + function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { + instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + return instantiationService.createInstance(NotebookEditorWidgetService); + } + function createFileUriFromPathFromRoot(path?: string): URI { const rootName = getRootName(); if (path) { From 4cd6107cf1509a2728ad25b559e29c500756bc97 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 8 Feb 2023 13:39:12 -0800 Subject: [PATCH 23/35] extra cleanup for match decorations --- .../contrib/find/findMatchDecorationModel.ts | 25 +++---------------- .../notebook/browser/notebookEditorWidget.ts | 4 --- .../contrib/search/browser/searchModel.ts | 17 +++++++------ 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 4669eddb4e4bf..02d327305322a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -17,7 +17,7 @@ export class FindMatchDecorationModel extends Disposable { private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; constructor( - private _notebookEditor: INotebookEditor | undefined, + private _notebookEditor: INotebookEditor, ) { super(); } @@ -26,14 +26,6 @@ export class FindMatchDecorationModel extends Disposable { return this._currentMatchDecorations; } - set notebookEditor(notebookEditor: INotebookEditor | undefined) { - if (this._notebookEditor) { - // clear any previous decorations if applicable - this.clearDecorations(); - } - this._notebookEditor = notebookEditor; - } - public clearDecorations() { this.clearCurrentFindMatchDecoration(); this.setAllFindMatchesDecorations([]); @@ -41,9 +33,6 @@ export class FindMatchDecorationModel extends Disposable { public async highlightCurrentFindMatchDecorationInCell(cell: ICellViewModel, cellRange: Range): Promise { - if (!this._notebookEditor) { - return null; - } this.clearCurrentFindMatchDecoration(); @@ -83,9 +72,7 @@ export class FindMatchDecorationModel extends Disposable { } public async highlightCurrentFindMatchDecorationInWebview(cell: ICellViewModel, index: number): Promise { - if (!this._notebookEditor) { - return null; - } + this.clearCurrentFindMatchDecoration(); const offset = await this._notebookEditor.highlightFind(index); @@ -108,9 +95,7 @@ export class FindMatchDecorationModel extends Disposable { } public clearCurrentFindMatchDecoration() { - if (!this._notebookEditor) { - return; - } + if (this._currentMatchDecorations?.kind === 'input') { this._notebookEditor.changeModelDecorations(accessor => { accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); @@ -125,9 +110,7 @@ export class FindMatchDecorationModel extends Disposable { public setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { - if (!this._notebookEditor) { - return; - } + this._notebookEditor.changeModelDecorations((accessor) => { const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 8e582f89aece6..01272dd17cf06 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -158,8 +158,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private readonly onDidRenderOutput = this._onDidRenderOutput.event; private readonly _onDidResizeOutputEmitter = this._register(new Emitter()); readonly onDidResizeOutput = this._onDidResizeOutputEmitter.event; - private readonly _onDidDispose = this._register(new Emitter()); - readonly onDidDispose = this._onDidDispose.event; //#endregion private _overlayContainer!: HTMLElement; @@ -2938,13 +2936,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._overlayContainer.remove(); this.viewModel?.dispose(); - this.viewModel = undefined; this._renderedEditors.clear(); this._baseCellEditorOptions.forEach(v => v.dispose()); this._baseCellEditorOptions.clear(); - this._onDidDispose.fire(); this._notebookOverviewRulerContainer.remove(); super.dispose(); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index c3d355ef8af49..b4b0edf552a72 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -277,7 +277,7 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _notebookUpdateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; - private _findMatchDecorationModel: FindMatchDecorationModel; + private _findMatchDecorationModel: FindMatchDecorationModel | undefined; private _context: Map = new Map(); public get context(): Map { @@ -304,7 +304,6 @@ export class FileMatch extends Disposable implements IFileMatch { this._updateScheduler = new RunOnceScheduler(this.updateMatchesForModel.bind(this), 250); this._notebookUpdateScheduler = new RunOnceScheduler(this.updateMatchesForEditorWidget.bind(this), 250); this._name = new Lazy(() => labelService.getUriBasenameLabel(this.resource)); - this._findMatchDecorationModel = new FindMatchDecorationModel(undefined); this.createMatches(); } @@ -366,7 +365,9 @@ export class FileMatch extends Disposable implements IFileMatch { } this._notebookEditorWidget = widget; - this._findMatchDecorationModel.notebookEditor = widget; + + this._findMatchDecorationModel?.dispose(); + this._findMatchDecorationModel = new FindMatchDecorationModel(widget); this._editorWidgetListener = this._notebookEditorWidget.textModel?.onDidChangeContent((e) => { if (!e.rawEvents.some(event => event.kind === NotebookCellsChangeType.ChangeCellContent || event.kind === NotebookCellsChangeType.ModelChange)) { return; @@ -384,7 +385,7 @@ export class FileMatch extends Disposable implements IFileMatch { this.updateMatchesForEditorWidget(); if (this._notebookEditorWidget) { this._notebookUpdateScheduler.cancel(); - this._findMatchDecorationModel.notebookEditor = undefined; + this._findMatchDecorationModel?.dispose(); this._editorWidgetListener?.dispose(); } this._notebookEditorWidget = null; @@ -460,8 +461,7 @@ export class FileMatch extends Disposable implements IFileMatch { } }); }); - - this._findMatchDecorationModel.setAllFindMatchesDecorations(matches); + this._findMatchDecorationModel?.setAllFindMatchesDecorations(matches); this._onChange.fire({ forceUpdateModel: modelChange }); } @@ -613,7 +613,7 @@ export class FileMatch extends Disposable implements IFileMatch { this.setSelectedMatch(null); this.unbindModel(); this.unbindEditorWidget(); - this._findMatchDecorationModel.dispose(); + this._findMatchDecorationModel?.dispose(); this._onDispose.fire(); super.dispose(); } @@ -624,6 +624,9 @@ export class FileMatch extends Disposable implements IFileMatch { } private async highlightCurrentFindMatchDecoration(match: NotebookMatch): Promise { + if (!this._findMatchDecorationModel) { + return null; + } if (match.webviewIndex === undefined) { return this._findMatchDecorationModel.highlightCurrentFindMatchDecorationInCell(match.cell, match.range()); } else { From 540974591a6de657aa34092a3d97c91d9f0806a8 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 8 Feb 2023 13:56:58 -0800 Subject: [PATCH 24/35] cleanup diffs --- .../notebook/browser/contrib/find/findMatchDecorationModel.ts | 2 -- src/vs/workbench/contrib/search/browser/searchModel.ts | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 02d327305322a..3d54e5e4a1387 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -95,7 +95,6 @@ export class FindMatchDecorationModel extends Disposable { } public clearCurrentFindMatchDecoration() { - if (this._currentMatchDecorations?.kind === 'input') { this._notebookEditor.changeModelDecorations(accessor => { accessor.deltaDecorations(this._currentMatchDecorations?.kind === 'input' ? this._currentMatchDecorations.decorations : [], []); @@ -110,7 +109,6 @@ export class FindMatchDecorationModel extends Disposable { public setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { - this._notebookEditor.changeModelDecorations((accessor) => { const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index b4b0edf552a72..201174d7afbcb 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -46,7 +46,6 @@ import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/ export class Match { private static readonly MAX_PREVIEW_CHARS = 250; - // need to make sure that IDs are different for same-line-different-cell stuff protected _id: string; protected _range: Range; private _oneLinePreviewText: string; @@ -407,8 +406,6 @@ export class FileMatch extends Disposable implements IFileMatch { } private async updateMatchesForEditorWidget(): Promise { - // this is called from a timeout and might fire - // after the model has been disposed if (!this._notebookEditorWidget) { return; } @@ -466,7 +463,6 @@ export class FileMatch extends Disposable implements IFileMatch { } private updateMatches(matches: FindMatch[], modelChange: boolean, model: ITextModel): void { - const textSearchResults = editorMatchesToTextSearchResults(matches, model, this._previewOptions); textSearchResults.forEach(textSearchResult => { textSearchResultToMatches(textSearchResult, this).forEach(match => { From a368db12b586ce842c219447f471914ef4962266 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 8 Feb 2023 15:15:47 -0800 Subject: [PATCH 25/35] remove comments and add readonly field --- .../notebook/browser/contrib/find/findMatchDecorationModel.ts | 2 +- .../workbench/contrib/search/test/browser/searchModel.test.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts index 3d54e5e4a1387..e5ac8c7adcb5c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findMatchDecorationModel.ts @@ -17,7 +17,7 @@ export class FindMatchDecorationModel extends Disposable { private _currentMatchDecorations: { kind: 'input'; decorations: ICellModelDecorations[] } | { kind: 'output'; index: number } | null = null; constructor( - private _notebookEditor: INotebookEditor, + private readonly _notebookEditor: INotebookEditor ) { super(); } diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 2cb9123f044a6..ca89c3854ae3e 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -84,9 +84,6 @@ suite('SearchModel', () => { instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); instantiationService.stub(ILogService, new NullLogService()); - // const config = new TestConfigurationService(); - // config.setUserConfiguration('search', { searchOnType: true }); - // instantiationService.stub(IConfigurationService, config); }); teardown(() => { From 917ac78cde6e980badaea09577fdf0405eae4387 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 10:06:31 -0800 Subject: [PATCH 26/35] remove webview extract match for now --- .../notebook/browser/notebookBrowser.ts | 2 +- .../browser/view/renderers/webviewMessages.ts | 2 +- .../browser/view/renderers/webviewPreloads.ts | 97 +------------------ .../search/browser/searchNotebookHelpers.ts | 9 +- 4 files changed, 13 insertions(+), 97 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 36d82c1104316..369cd5c09a5cf 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -740,7 +740,7 @@ export interface ISearchPreviewInfo { export interface CellWebviewFindMatch { readonly index: number; - readonly searchPreviewInfo: ISearchPreviewInfo; + readonly searchPreviewInfo?: ISearchPreviewInfo; } export type CellContentFindMatch = FindMatch; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 9f01237fa5378..3e46646d23e05 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -424,7 +424,7 @@ export interface IFindMatch { readonly cellId: string; readonly id: string; readonly index: number; - readonly searchPreviewInfo: ISearchPreviewInfo; + readonly searchPreviewInfo?: ISearchPreviewInfo; } export interface IDidFindMessage extends BaseToWebviewMessage { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 965e10973258e..46a103a4875cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -812,18 +812,9 @@ async function webviewPreloads(ctx: PreloadContext) { container: Node; originalRange: Range; isShadow: boolean; - searchPreviewInfo: ISearchPreviewInfo; highlightResult?: IHighlightResult; } - interface ISearchPreviewInfo { - line: string; - range: { - start: number; - end: number; - }; - } - interface IHighlighter { highlightCurrentMatch(index: number): void; unHighlightCurrentMatch(index: number): void; @@ -948,81 +939,6 @@ async function webviewPreloads(ctx: PreloadContext) { } } - function extractSelectionLine(selection: Selection): ISearchPreviewInfo { - const range = selection.getRangeAt(0); - - // we need to keep a reference to the old selection range to re-apply later - const oldRange = range.cloneRange(); - const captureLength = selection.toString().length; - - // use selection API to modify selection to get entire line (the first line if multi-select) - - // collapse selection to start so that the cursor position is at beginning of match - selection.collapseToStart(); - - // extend selection in both directions to select the line - selection.modify('move', 'backward', 'lineboundary'); - selection.modify('extend', 'forward', 'lineboundary'); - - const line = selection.toString(); - - // using the original range and the new range, we can find the offset of the match from the line start. - const rangeStart = getStartOffset(selection.getRangeAt(0), oldRange); - - // line range for match - const lineRange = { - start: rangeStart, - end: rangeStart + captureLength, - }; - - // re-add the old range so that the selection is restored - selection.removeAllRanges(); - selection.addRange(oldRange); - - return { line, range: lineRange }; - } - - function getStartOffset(lineRange: Range, originalRange: Range) { - // sometimes, the old and new range are in different DOM elements (ie: when the match is inside of ) - // so we need to find the first common ancestor DOM element and find the positions of the old and new range relative to that. - const firstCommonAncestor = findFirstCommonAncestor(lineRange.startContainer, originalRange.startContainer); - - const selectionOffset = getSelectionOffsetRelativeTo(firstCommonAncestor, lineRange.startContainer) + lineRange.startOffset; - const textOffset = getSelectionOffsetRelativeTo(firstCommonAncestor, originalRange.startContainer) + originalRange.startOffset; - return textOffset - selectionOffset; - } - - // modified from https://stackoverflow.com/a/68583466/16253823 - function findFirstCommonAncestor(nodeA: Node, nodeB: Node) { - const range = new Range(); - range.setStart(nodeA, 0); - range.setEnd(nodeB, 0); - return range.commonAncestorContainer; - } - - // modified from https://stackoverflow.com/a/48812529/16253823 - function getSelectionOffsetRelativeTo(parentElement: Node, currentNode: Node | null): number { - if (!currentNode) { - return 0; - } - let offset = 0; - - if (currentNode === parentElement || !parentElement.contains(currentNode)) { - return offset; - } - - - // count the number of chars before the current dom elem and the start of the dom - let prevSibling = currentNode.previousSibling; - while (prevSibling) { - const nodeContent = prevSibling.nodeValue || ''; - offset += nodeContent.length; - prevSibling = prevSibling.previousSibling; - } - - return offset + getSelectionOffsetRelativeTo(parentElement, currentNode.parentNode); - } - const find = (query: string, options: { wholeWord?: boolean; caseSensitive?: boolean; includeMarkup: boolean; includeOutput: boolean }) => { let find = true; const matches: IFindMatch[] = []; @@ -1069,7 +985,6 @@ async function webviewPreloads(ctx: PreloadContext) { container: preview, isShadow: true, originalRange: shadowSelection.getRangeAt(0), - searchPreviewInfo: extractSelectionLine(shadowSelection), }); } } @@ -1089,8 +1004,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, container: outputNode, isShadow: true, - originalRange: shadowSelection.getRangeAt(0), - searchPreviewInfo: extractSelectionLine(shadowSelection) + originalRange: shadowSelection.getRangeAt(0) }); } } @@ -1108,8 +1022,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: lastEl.cellId, container: lastEl.container, isShadow: false, - originalRange: selection.getRangeAt(0), - searchPreviewInfo: extractSelectionLine(selection), + originalRange: selection.getRangeAt(0) }); } else { @@ -1129,8 +1042,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, container: node, isShadow: false, - originalRange: selection.getRangeAt(0), - searchPreviewInfo: extractSelectionLine(selection), + originalRange: selection.getRangeAt(0) }); } break; @@ -1166,8 +1078,7 @@ async function webviewPreloads(ctx: PreloadContext) { type: match.type, id: match.id, cellId: match.cellId, - index, - searchPreviewInfo: match.searchPreviewInfo, + index })) }); }; diff --git a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts index f02e122c820d4..84c48e8a41279 100644 --- a/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts +++ b/src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts @@ -50,9 +50,14 @@ function notebookEditorMatchToTextSearchResult(cellInfo: CellFindMatchInfoForTex } } else { + // TODO: this is a placeholder for webview matches + const searchPreviewInfo = matches.searchPreviewInfo ?? { + line: '', range: { start: 0, end: 0 } + }; + return new NotebookTextSearchMatch( - matches.searchPreviewInfo.line, - new Range(0, matches.searchPreviewInfo.range.start, 0, matches.searchPreviewInfo.range.end), + searchPreviewInfo.line, + new Range(0, searchPreviewInfo.range.start, 0, searchPreviewInfo.range.end), cellInfo.notebookMatchInfo, previewOptions); } From aa79ffcefb6b9e93a0508aad1295a750297b2a72 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 10:10:33 -0800 Subject: [PATCH 27/35] remove comma --- .../contrib/notebook/browser/view/renderers/webviewPreloads.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 46a103a4875cb..485b6f262cc52 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -984,7 +984,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: preview.id, container: preview, isShadow: true, - originalRange: shadowSelection.getRangeAt(0), + originalRange: shadowSelection.getRangeAt(0) }); } } From 99c2baa86bb481fafe66229285f260d8d7ce5d79 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 12:26:25 -0800 Subject: [PATCH 28/35] use onDidAddNotebookEditor --- .../browser/services/notebookEditorService.ts | 1 - .../services/notebookEditorServiceImpl.ts | 4 ---- .../contrib/search/browser/searchModel.ts | 16 +++++++++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts index 92c3b01e76d4f..c9f22f1f832de 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorService.ts @@ -27,7 +27,6 @@ export interface INotebookEditorService { retrieveAllExistingWidgets(): IBorrowValue[]; onDidAddNotebookEditor: Event; onDidRemoveNotebookEditor: Event; - onDidAddNotebookEditorWidget: Event; addNotebookEditor(editor: INotebookEditor): void; removeNotebookEditor(editor: INotebookEditor): void; getNotebookEditor(editorId: string): INotebookEditor | undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index a1fbf03208a9c..e61cfbe818125 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -30,9 +30,6 @@ export class NotebookEditorWidgetService implements INotebookEditorService { readonly onDidAddNotebookEditor = this._onNotebookEditorAdd.event; readonly onDidRemoveNotebookEditor = this._onNotebookEditorsRemove.event; - private readonly _onDidAddNotebookEditorWidget = new Emitter(); - readonly onDidAddNotebookEditorWidget = this._onDidAddNotebookEditorWidget.event; - private readonly _borrowableEditors = new Map>(); constructor( @@ -161,7 +158,6 @@ export class NotebookEditorWidgetService implements INotebookEditorService { // NEW widget const instantiationService = accessor.get(IInstantiationService); const widget = instantiationService.createInstance(NotebookEditorWidget, creationOptions ?? getDefaultNotebookCreationOptions(), initialDimension); - this._onDidAddNotebookEditorWidget.fire(widget); const token = this._tokenPool++; value = { widget, token }; diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 201174d7afbcb..169e71f639b34 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1299,7 +1299,15 @@ export class SearchResult extends Disposable { this._rangeHighlightDecorations = this.instantiationService.createInstance(RangeHighlightDecorations); this._register(this.modelService.onModelAdded(model => this.onModelAdded(model))); - this._register(this.notebookEditorService.onDidAddNotebookEditorWidget(widget => this.onDidAddNotebookEditorWidget(widget))); + + const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; + if (experimentalNotebooksEnabled) { + this._register(this.notebookEditorService.onDidAddNotebookEditor(widget => { + if (widget instanceof NotebookEditorWidget) { + this.onDidAddNotebookEditorWidget(widget); + } + })); + } this._register(this.onChange(e => { if (e.removed) { @@ -1405,12 +1413,6 @@ export class SearchResult extends Disposable { } private onDidAddNotebookEditorWidget(widget: NotebookEditorWidget): void { - const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; - - if (!experimentalNotebooksEnabled) { - return; - } - widget.onWillChangeModel( (model) => { if (model) { From 3cbc5afaae34554ca4f6fabeaf1b8235153b0938 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 17:42:03 -0800 Subject: [PATCH 29/35] add simple tests --- .../search/test/browser/searchResult.test.ts | 56 ++++++++++----- .../browser/searchNotebookHelpers.test.ts | 68 +++++++++++++++++++ 2 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 4b81f2a8651aa..fe3d3a8a02d0e 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Match, FileMatch, SearchResult, SearchModel, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { Match, FileMatch, SearchResult, SearchModel, FolderMatch, NotebookMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -29,6 +29,9 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/se import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; +import { NotebookTextSearchMatch } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -220,6 +223,42 @@ suite('SearchResult', () => { assert.ok(new Range(2, 1, 2, 2).equalsRange(actuaMatches[0].range())); }); + test('Adding multiple raw notebook matches', function () { + const testObject = aSearchResult(); + + const modelTarget = instantiationService.spy(IModelService, 'getModel'); + const cell = { cellKind: CellKind.Code } as ICellViewModel; + const target = [ + aRawMatch('/1', + new NotebookTextSearchMatch('preview 1', new OneLineRange(1, 1, 4), { + cellIndex: 0, + matchStartIndex: 0, + matchEndIndex: 1, + cell, + }), + new NotebookTextSearchMatch('preview 1', new OneLineRange(1, 4, 11), { + cellIndex: 0, + matchStartIndex: 0, + matchEndIndex: 1, + cell, + })), + aRawMatch('/2', + new NotebookTextSearchMatch('preview 2', lineOneRange, { + cellIndex: 0, + matchStartIndex: 0, + matchEndIndex: 1, + cell, + }))]; + + testObject.add(target); + assert.strictEqual(3, testObject.count()); + + // when a model is binded, the results are queried once again. + assert.ok(modelTarget.calledTwice); + assert.ok(modelTarget.calledWith(testObject.matches()[0].resource)); + assert.ok(modelTarget.calledWith(testObject.matches()[1].resource)); + }); + test('Dispose disposes matches', function () { const target1 = sinon.spy(); const target2 = sinon.spy(); @@ -273,21 +312,6 @@ suite('SearchResult', () => { assert.deepStrictEqual([{ elements: arrayToRemove, removed: true }], target.args[0]); }); - test('remove triggers change event', function () { - const target = sinon.spy(); - const testObject = aSearchResult(); - testObject.add([ - aRawMatch('/1', - new TextSearchMatch('preview 1', lineOneRange))]); - const objectToRemove = testObject.matches()[0]; - testObject.onChange(target); - - testObject.remove(objectToRemove); - - assert.ok(target.calledOnce); - assert.deepStrictEqual([{ elements: [objectToRemove], removed: true }], target.args[0]); - }); - test('Removing all line matches and adding back will add file back to result', function () { const testObject = aSearchResult(); testObject.add([ diff --git a/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts new file mode 100644 index 0000000000000..4c73036a328a4 --- /dev/null +++ b/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Range } from 'vs/editor/common/core/range'; +import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; +import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; +import { ISearchRange } from 'vs/workbench/services/search/common/search'; +import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; + +suite('searchNotebookHelpers', () => { + let instantiationService: TestInstantiationService; + setup(() => { + instantiationService = new TestInstantiationService(); + }); + suite('notebookEditorMatchesToTextSearchResults', () => { + + function assertRangesEqual(actual: ISearchRange | ISearchRange[], expected: ISearchRange[]) { + if (!Array.isArray(actual)) { + // All of these tests are for arrays... + throw new Error('Expected array of ranges'); + } + + assert.strictEqual(actual.length, expected.length); + + // These are sometimes Range, sometimes SearchRange + actual.forEach((r, i) => { + const expectedRange = expected[i]; + assert.deepStrictEqual( + { startLineNumber: r.startLineNumber, startColumn: r.startColumn, endLineNumber: r.endLineNumber, endColumn: r.endColumn }, + { startLineNumber: expectedRange.startLineNumber, startColumn: expectedRange.startColumn, endLineNumber: expectedRange.endLineNumber, endColumn: expectedRange.endColumn }); + }); + } + + test('simple', () => { + const cell = { + cellKind: CellKind.Code, textBuffer: { + getLineContent(lineNumber: number): string { + return 'test'; + } + } + } as ICellViewModel; + + const findMatch = new FindMatch(new Range(5, 1, 5, 2), null); + const cellFindMatchWithIndex: CellFindMatchWithIndex = { + cell, + index: 0, + length: 5, + getMatch(index: number): FindMatch | CellWebviewFindMatch { + return findMatch; + }, + contentMatches: [findMatch], + webviewMatches: [] + }; + + const results = notebookEditorMatchesToTextSearchResults([cellFindMatchWithIndex]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].preview.text, 'test\n'); + assertRangesEqual(results[0].preview.matches, [new Range(0, 0, 0, 1)]); + assertRangesEqual(results[0].ranges, [new Range(4, 0, 4, 1)]); + }); + + }); +}); From b4c213e352ea14fb2408df2e6d845caf40bbc6bb Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 17:45:12 -0800 Subject: [PATCH 30/35] test corrections --- .../workbench/contrib/search/test/browser/searchResult.test.ts | 2 +- .../services/search/test/browser/searchNotebookHelpers.test.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index fe3d3a8a02d0e..8ba2465a21a56 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Match, FileMatch, SearchResult, SearchModel, FolderMatch, NotebookMatch } from 'vs/workbench/contrib/search/browser/searchModel'; +import { Match, FileMatch, SearchResult, SearchModel, FolderMatch } from 'vs/workbench/contrib/search/browser/searchModel'; import { URI } from 'vs/base/common/uri'; import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts index 4c73036a328a4..96c3c4b59f800 100644 --- a/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts @@ -10,12 +10,9 @@ import { notebookEditorMatchesToTextSearchResults } from 'vs/workbench/contrib/s import { ISearchRange } from 'vs/workbench/services/search/common/search'; import { CellFindMatchWithIndex, ICellViewModel, CellWebviewFindMatch } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; suite('searchNotebookHelpers', () => { - let instantiationService: TestInstantiationService; setup(() => { - instantiationService = new TestInstantiationService(); }); suite('notebookEditorMatchesToTextSearchResults', () => { From 2bfad50489ce6ed03d731f1e0fa5d489e027b6a9 Mon Sep 17 00:00:00 2001 From: andreamah Date: Thu, 9 Feb 2023 17:47:27 -0800 Subject: [PATCH 31/35] move notebooksearchhelper tests --- .../search/test/browser/searchNotebookHelpers.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/workbench/{services => contrib}/search/test/browser/searchNotebookHelpers.test.ts (100%) diff --git a/src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts similarity index 100% rename from src/vs/workbench/services/search/test/browser/searchNotebookHelpers.test.ts rename to src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts From 9d16b921f58213eb08f2038626d302f3f928e6f2 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 10 Feb 2023 10:32:53 -0800 Subject: [PATCH 32/35] PR feedback --- .../contrib/search/browser/replaceService.ts | 2 +- .../contrib/search/browser/searchModel.ts | 38 ++++++++++--------- .../contrib/search/browser/searchView.ts | 2 +- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 242dcff1b5ac3..b010954cd4f6f 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -116,7 +116,7 @@ export class ReplaceService implements IReplaceService { if (notebookResource) { return this.editorService.save([...this.editorService.findEditors(notebookResource)]); } else { - return Promise.resolve(); + return; } } else { return this.textFileService.files.get(e.resource)?.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 169e71f639b34..69da46af64a21 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -315,7 +315,7 @@ export class FileMatch extends Disposable implements IFileMatch { const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; const notebookEditorWidgetBorrow = experimentalNotebooksEnabled ? this.notebookEditorService.retrieveExistingWidgetFromURI(this._resource) : undefined; if (notebookEditorWidgetBorrow?.value) { - await this.bindEditorWidget(notebookEditorWidgetBorrow.value); + await this.bindNotebookEditorWidget(notebookEditorWidgetBorrow.value); } else if (model) { this.bindModel(model); this.updateMatchesForModel(); @@ -357,7 +357,7 @@ export class FileMatch extends Disposable implements IFileMatch { } } - async bindEditorWidget(widget: NotebookEditorWidget) { + async bindNotebookEditorWidget(widget: NotebookEditorWidget) { if (this._notebookEditorWidget === widget) { return; @@ -376,7 +376,7 @@ export class FileMatch extends Disposable implements IFileMatch { await this.updateMatchesForEditorWidget(); } - unbindEditorWidget(widget?: NotebookEditorWidget) { + unbindNotebookEditorWidget(widget?: NotebookEditorWidget) { if (widget && this._notebookEditorWidget !== widget) { return; } @@ -608,7 +608,7 @@ export class FileMatch extends Disposable implements IFileMatch { override dispose(): void { this.setSelectedMatch(null); this.unbindModel(); - this.unbindEditorWidget(); + this.unbindNotebookEditorWidget(); this._findMatchDecorationModel?.dispose(); this._onDispose.fire(); super.dispose(); @@ -740,28 +740,28 @@ export class FolderMatch extends Disposable { } } - async bindEditorWidget(editor: NotebookEditorWidget, resource: URI) { + async bindNotebookEditorWidget(editor: NotebookEditorWidget, resource: URI) { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - await fileMatch.bindEditorWidget(editor); + await fileMatch.bindNotebookEditorWidget(editor); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { - await elem.bindEditorWidget(editor, resource); + await elem.bindNotebookEditorWidget(editor, resource); } } } - unbindEditorWidget(editor: NotebookEditorWidget, resource: URI) { + unbindNotebookEditorWidget(editor: NotebookEditorWidget, resource: URI) { const fileMatch = this._fileMatches.get(resource); if (fileMatch) { - fileMatch.unbindEditorWidget(editor); + fileMatch.unbindNotebookEditorWidget(editor); } else { const folderMatches = this.folderMatchesIterator(); for (const elem of folderMatches) { - elem.unbindEditorWidget(editor, resource); + elem.unbindNotebookEditorWidget(editor, resource); } } @@ -1285,6 +1285,7 @@ export class SearchResult extends Disposable { private _rangeHighlightDecorations: RangeHighlightDecorations; private disposePastResults: () => void = () => { }; private _isDirty = false; + private _onWillChangeModelListener: IDisposable | undefined; constructor( private _searchModel: SearchModel, @@ -1413,7 +1414,8 @@ export class SearchResult extends Disposable { } private onDidAddNotebookEditorWidget(widget: NotebookEditorWidget): void { - widget.onWillChangeModel( + this._onWillChangeModelListener?.dispose(); + this._onWillChangeModelListener = widget.onWillChangeModel( (model) => { if (model) { this.onNotebookEditorWidgetRemoved(widget, model?.uri); @@ -1438,12 +1440,12 @@ export class SearchResult extends Disposable { private async onNotebookEditorWidgetAdded(editor: NotebookEditorWidget, resource: URI): Promise { const folderMatch = this._folderMatchesMap.findSubstr(resource); - await folderMatch?.bindEditorWidget(editor, resource); + await folderMatch?.bindNotebookEditorWidget(editor, resource); } private onNotebookEditorWidgetRemoved(editor: NotebookEditorWidget, resource: URI): void { const folderMatch = this._folderMatchesMap.findSubstr(resource); - folderMatch?.unbindEditorWidget(editor, resource); + folderMatch?.unbindNotebookEditorWidget(editor, resource); } private _createBaseFolderMatch(resource: URI | null, id: string, index: number, query: ITextQuery): FolderMatch { @@ -1710,7 +1712,7 @@ export class SearchModel extends Disposable { return this._searchResult; } - private async getLocalNotebookResults(query: ITextQuery): Promise<{ results: ResourceMap; limitHit: boolean }> { + private async getLocalNotebookResults(query: ITextQuery, token: CancellationToken): Promise<{ results: ResourceMap; limitHit: boolean }> { const localResults = new ResourceMap(uri => this.uriIdentityService.extUri.getComparisonKey(uri)); let limitHit = false; @@ -1732,7 +1734,7 @@ export class SearchModel extends Disposable { includeMarkupPreview: false, includeCodeInput: true, includeOutput: false, - }, CancellationToken.None); + }, token); if (matches.length) { @@ -1754,8 +1756,8 @@ export class SearchModel extends Disposable { }; } - async notebookSearch(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { - const localResults = await this.getLocalNotebookResults(query); + async notebookSearch(query: ITextQuery, token: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { + const localResults = await this.getLocalNotebookResults(query, token); if (onProgress) { arrays.coalesce([...localResults.results.values()]).forEach(onProgress); @@ -1778,7 +1780,7 @@ export class SearchModel extends Disposable { }; const experimentalNotebooksEnabled = this.configurationService.getValue('search').experimental.notebookSearch; - const notebookResult = experimentalNotebooksEnabled ? await this.notebookSearch(query, onProgressCall) : { messages: [], results: [] }; + const notebookResult = experimentalNotebooksEnabled ? await this.notebookSearch(query, this.currentCancelTokenSource.token, onProgressCall) : { messages: [], results: [] }; const currentResult = await this.searchService.textSearch( searchQuery, this.currentCancelTokenSource.token, onProgressCall, diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index a31d8c5fbefe1..c5a7b74913eed 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1823,7 +1823,7 @@ export class SearchView extends ViewPane { if (editorWidget) { // Ensure that the editor widget is binded. If if is, then this should return immediately. // Otherwise, it will bind the widget. - await element.parent().bindEditorWidget(editorWidget); + await element.parent().bindNotebookEditorWidget(editorWidget); const matchIndex = oldParentMatches.findIndex(e => e.id() === element.id()); const matches = element.parent().matches(); From 8255fc573827eadc5cecec39427283aabcb7da7e Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 10 Feb 2023 10:37:44 -0800 Subject: [PATCH 33/35] add listener dispose --- src/vs/workbench/contrib/search/browser/searchModel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 69da46af64a21..06e7a0e3d17a7 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -1640,6 +1640,7 @@ export class SearchResult extends Disposable { } override dispose(): void { + this._onWillChangeModelListener?.dispose(); this.disposePastResults(); this.disposeMatches(); this._rangeHighlightDecorations.dispose(); From 350ed4982d46a9a1f10831c0f88998e0a9f3d3c6 Mon Sep 17 00:00:00 2001 From: andreamah Date: Fri, 10 Feb 2023 11:51:03 -0800 Subject: [PATCH 34/35] get notebook model for replace using notebookEditorModelResolverService --- src/vs/workbench/contrib/search/browser/replaceService.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index b010954cd4f6f..09bae9833f02b 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -28,6 +28,7 @@ import { dirname } from 'vs/base/common/resources'; import { Promises } from 'vs/base/common/async'; import { SaveSourceRegistry } from 'vs/workbench/common/editor'; import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; const REPLACE_PREVIEW = 'replacePreview'; @@ -100,7 +101,8 @@ export class ReplaceService implements IReplaceService { @IEditorService private readonly editorService: IEditorService, @ITextModelService private readonly textModelResolverService: ITextModelService, @IBulkEditService private readonly bulkEditorService: IBulkEditService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @INotebookEditorModelResolverService private readonly notebookEditorModelResolverService: INotebookEditorModelResolverService ) { } replace(match: Match): Promise; @@ -114,7 +116,9 @@ export class ReplaceService implements IReplaceService { if (e.resource.scheme === network.Schemas.vscodeNotebookCell) { const notebookResource = CellUri.parse(e.resource)?.notebook; if (notebookResource) { - return this.editorService.save([...this.editorService.findEditors(notebookResource)]); + // todo: find whether there is a common API for saving notebooks and text files + const ref = await this.notebookEditorModelResolverService.resolve(notebookResource); + return ref.object.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); } else { return; } From 3c947371c577b940c52e25e501c6bc9ce0920cea Mon Sep 17 00:00:00 2001 From: andreamah Date: Mon, 13 Feb 2023 08:46:05 -0800 Subject: [PATCH 35/35] dispose reference to notebook in replaceService --- src/vs/workbench/contrib/search/browser/replaceService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 09bae9833f02b..c991021c2b9bc 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -118,10 +118,10 @@ export class ReplaceService implements IReplaceService { if (notebookResource) { // todo: find whether there is a common API for saving notebooks and text files const ref = await this.notebookEditorModelResolverService.resolve(notebookResource); - return ref.object.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); - } else { - return; + await ref.object.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); + ref.dispose(); } + return; } else { return this.textFileService.files.get(e.resource)?.save({ source: ReplaceService.REPLACE_SAVE_SOURCE }); }