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 0e289d1ed71..0419aa607fc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; -import { INotebookEditor, CellFindMatch, CellEditState, CellFindMatchWithIndex, OutputFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditor, CellEditState, CellFindMatchWithIndex, CellWebviewFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, INotebookDeltaDecoration, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Range } from 'vs/editor/common/core/range'; import { FindDecorations } from 'vs/editor/contrib/find/browser/findDecorations'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -20,9 +20,45 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; import { overviewRulerFindMatchForeground, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; +export class CellFindMatchModel implements CellFindMatchWithIndex { + readonly cell: ICellViewModel; + readonly index: number; + private _contentMatches: FindMatch[]; + private _webviewMatches: CellWebviewFindMatch[]; + get length() { + return this._contentMatches.length + this._webviewMatches.length; + } + + get contentMatches(): FindMatch[] { + return this._contentMatches; + } + + get webviewMatches(): CellWebviewFindMatch[] { + return this._webviewMatches; + } + + constructor(cell: ICellViewModel, index: number, contentMatches: FindMatch[], webviewMatches: CellWebviewFindMatch[]) { + this.cell = cell; + this.index = index; + this._contentMatches = contentMatches; + this._webviewMatches = webviewMatches; + } + + getMatch(index: number) { + if (index >= this.length) { + throw new Error('NotebookCellFindMatch: index out of range'); + } + + if (index < this._contentMatches.length) { + return this._contentMatches[index]; + } + + return this._webviewMatches[index - this._contentMatches.length]; + } +} export class FindModel extends Disposable { - private _findMatches: CellFindMatch[] = []; + private _findMatches: CellFindMatchWithIndex[] = []; protected _findMatchesStarts: PrefixSumComputer | null = null; private _currentMatch: number = -1; private _allMatchesDecorations: ICellModelDecorations[] = []; @@ -79,12 +115,12 @@ export class FindModel extends Disposable { getCurrentMatch() { const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); const cell = this._findMatches[nextIndex.index].cell; - const match = this._findMatches[nextIndex.index].matches[nextIndex.remainder]; + const match = this._findMatches[nextIndex.index].getMatch(nextIndex.remainder); return { cell, match, - isModelMatch: nextIndex.remainder < this._findMatches[nextIndex.index].modelMatchCount + isModelMatch: nextIndex.remainder < this._findMatches[nextIndex.index].contentMatches.length }; } @@ -96,7 +132,7 @@ export class FindModel extends Disposable { } const findMatch = this.findMatches[findMatchIndex]; - const index = findMatch.matches.slice(0, findMatch.modelMatchCount).findIndex(match => (match as FindMatch).range.intersectRanges(focus.range) !== null); + const index = findMatch.contentMatches.findIndex(match => match.range.intersectRanges(focus.range) !== null); if (index === undefined) { return; @@ -110,7 +146,7 @@ export class FindModel extends Disposable { this._state.changeMatchInfo( this._currentMatch, - this._findMatches.reduce((p, c) => p + c.matches.length, 0), + this._findMatches.reduce((p, c) => p + c.length, 0), undefined ); }); @@ -149,7 +185,7 @@ export class FindModel extends Disposable { this._state.changeMatchInfo( this._currentMatch, - this._findMatches.reduce((p, c) => p + c.matches.length, 0), + this._findMatches.reduce((p, c) => p + c.length, 0), undefined ); }); @@ -157,7 +193,7 @@ export class FindModel extends Disposable { private revealCellRange(cellIndex: number, matchIndex: number, outputOffset: number | null) { const findMatch = this._findMatches[cellIndex]; - if (matchIndex >= findMatch.modelMatchCount) { + if (matchIndex >= findMatch.contentMatches.length) { // reveal output range this._notebookEditor.focusElement(findMatch.cell); const index = this._notebookEditor.getCellIndex(findMatch.cell); @@ -166,7 +202,7 @@ export class FindModel extends Disposable { this._notebookEditor.revealCellOffsetInCenterAsync(findMatch.cell, outputOffset ?? 0); } } else { - const match = findMatch.matches[matchIndex] as FindMatch; + const match = findMatch.getMatch(matchIndex) as FindMatch; findMatch.cell.updateEditState(CellEditState.Editing, 'find'); findMatch.cell.isInputCollapsed = false; this._notebookEditor.focusElement(findMatch.cell); @@ -290,14 +326,14 @@ export class FindModel extends Disposable { // there are still some search results in current cell let currMatchRangeInEditor = cell.editorAttached && currentMatchDecorationId.decorations[0] ? cell.getCellDecorationRange(currentMatchDecorationId.decorations[0]) : null; - if (currMatchRangeInEditor === null && oldCurrIndex.remainder < this._findMatches[oldCurrIndex.index].modelMatchCount) { - currMatchRangeInEditor = (this._findMatches[oldCurrIndex.index].matches[oldCurrIndex.remainder] as FindMatch).range; + if (currMatchRangeInEditor === null && oldCurrIndex.remainder < this._findMatches[oldCurrIndex.index].contentMatches.length) { + currMatchRangeInEditor = (this._findMatches[oldCurrIndex.index].getMatch(oldCurrIndex.remainder) as FindMatch).range; } if (currMatchRangeInEditor !== null) { // we find a range for the previous current match, let's find the nearest one after it (can overlap) const cellMatch = findMatches[matchAfterSelection]; - const matchAfterOldSelection = findFirstInSorted(cellMatch.matches.slice(0, cellMatch.modelMatchCount), match => Range.compareRangesUsingStarts((match as FindMatch).range, currMatchRangeInEditor) >= 0); + const matchAfterOldSelection = findFirstInSorted(cellMatch.contentMatches, match => Range.compareRangesUsingStarts((match as FindMatch).range, currMatchRangeInEditor) >= 0); this._updateCurrentMatch(findMatches, this._matchesCountBeforeIndex(findMatches, matchAfterSelection) + matchAfterOldSelection); } else { // no range found, let's fall back to finding the nearest match @@ -312,7 +348,7 @@ export class FindModel extends Disposable { } } - private set(cellFindMatches: CellFindMatch[] | null, autoStart: boolean): void { + private set(cellFindMatches: CellFindMatchWithIndex[] | null, autoStart: boolean): void { if (!cellFindMatches || !cellFindMatches.length) { this._findMatches = []; this.setAllFindMatchesDecorations([]); @@ -323,7 +359,7 @@ export class FindModel extends Disposable { this._state.changeMatchInfo( this._currentMatch, - this._findMatches.reduce((p, c) => p + c.matches.length, 0), + this._findMatches.reduce((p, c) => p + c.length, 0), undefined ); return; @@ -343,7 +379,7 @@ export class FindModel extends Disposable { this._state.changeMatchInfo( this._currentMatch, - this._findMatches.reduce((p, c) => p + c.matches.length, 0), + this._findMatches.reduce((p, c) => p + c.length, 0), undefined ); } @@ -385,7 +421,7 @@ export class FindModel extends Disposable { this._state.changeMatchInfo( this._currentMatch, - this._findMatches.reduce((p, c) => p + c.matches.length, 0), + this._findMatches.reduce((p, c) => p + c.length, 0), undefined ); } @@ -393,7 +429,7 @@ export class FindModel extends Disposable { private _matchesCountBeforeIndex(findMatches: CellFindMatchWithIndex[], index: number) { let prevMatchesCount = 0; for (let i = 0; i < index; i++) { - prevMatchesCount += findMatches[i].matches.length; + prevMatchesCount += findMatches[i].length; } return prevMatchesCount; @@ -403,7 +439,7 @@ export class FindModel extends Disposable { if (this._findMatches && this._findMatches.length) { const values = new Uint32Array(this._findMatches.length); for (let i = 0; i < this._findMatches.length; i++) { - values[i] = this._findMatches[i].matches.length; + values[i] = this._findMatches[i].length; } this._findMatchesStarts = new PrefixSumComputer(values); @@ -415,10 +451,10 @@ export class FindModel extends Disposable { private async highlightCurrentFindMatchDecoration(cellIndex: number, matchIndex: number): Promise { const cell = this._findMatches[cellIndex].cell; - if (matchIndex < this._findMatches[cellIndex].modelMatchCount) { + if (matchIndex < this._findMatches[cellIndex].contentMatches.length) { this.clearCurrentFindMatchDecoration(); - const match = this._findMatches[cellIndex].matches[matchIndex] as FindMatch; + const match = this._findMatches[cellIndex].getMatch(matchIndex) as FindMatch; // 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 => { @@ -454,7 +490,7 @@ export class FindModel extends Disposable { } else { this.clearCurrentFindMatchDecoration(); - const match = this._findMatches[cellIndex].matches[matchIndex] as OutputFindMatch; + const match = this._findMatches[cellIndex].getMatch(matchIndex) as CellWebviewFindMatch; const offset = await this._notebookEditor.highlightFind(cell, match.index); this._currentMatchDecorations = { kind: 'output', index: match.index }; @@ -487,19 +523,17 @@ export class FindModel extends Disposable { this._currentMatchCellDecorations = this._notebookEditor.deltaCellDecorations(this._currentMatchCellDecorations, []); } - private setAllFindMatchesDecorations(cellFindMatches: CellFindMatch[]) { + private setAllFindMatchesDecorations(cellFindMatches: CellFindMatchWithIndex[]) { this._notebookEditor.changeModelDecorations((accessor) => { const findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION; const deltaDecorations: ICellModelDeltaDecorations[] = cellFindMatches.map(cellFindMatch => { - const findMatches = cellFindMatch.matches; - // Find matches - const newFindMatchesDecorations: IModelDeltaDecoration[] = new Array(findMatches.length); - for (let i = 0, len = Math.min(findMatches.length, cellFindMatch.modelMatchCount); i < len; i++) { + const newFindMatchesDecorations: IModelDeltaDecoration[] = new Array(cellFindMatch.length); + for (let i = 0; i < cellFindMatch.contentMatches.length; i++) { newFindMatchesDecorations[i] = { - range: (findMatches[i] as FindMatch).range, + range: cellFindMatch.contentMatches[i].range, options: findMatchesOptions }; } @@ -517,8 +551,8 @@ export class FindModel extends Disposable { options: { overviewRuler: { color: overviewRulerFindMatchForeground, - modelRanges: cellFindMatch.matches.slice(0, cellFindMatch.modelMatchCount).map(match => (match as FindMatch).range), - includeOutput: cellFindMatch.modelMatchCount < cellFindMatch.matches.length + modelRanges: cellFindMatch.contentMatches.map(match => match.range), + includeOutput: cellFindMatch.webviewMatches.length > 0 } } }; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index 3589f801003..fa489b9015a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -109,7 +109,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi } const matches = this._findModel.findMatches; - this._replaceAllBtn.setEnabled(matches.length > 0 && matches.find(match => match.modelMatchCount < match.matches.length) === undefined); + this._replaceAllBtn.setEnabled(matches.length > 0 && matches.find(match => match.webviewMatches.length > 0) === undefined); if (e.filters) { this._findInput.updateFilterState((this._state.filters?.markupPreview ?? false) || (this._state.filters?.codeOutput ?? false)); @@ -200,13 +200,9 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi const cellFindMatches = this._findModel.findMatches; const replaceStrings: string[] = []; cellFindMatches.forEach(cellFindMatch => { - const findMatches = cellFindMatch.matches; - findMatches.forEach((findMatch, index) => { - if (index < cellFindMatch.modelMatchCount) { - const match = findMatch as FindMatch; - const matches = match.matches; - replaceStrings.push(replacePattern.buildReplaceString(matches, this._state.preserveCase)); - } + cellFindMatch.contentMatches.forEach(match => { + const matches = match.matches; + replaceStrings.push(replacePattern.buildReplaceString(matches, this._state.preserveCase)); }); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d0ef02c47e9..fa24f33cda1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -397,7 +397,7 @@ export interface INotebookViewModel { deltaCellStatusBarItems(oldItems: string[], newItems: INotebookDeltaCellStatusBarItems[]): string[]; getFoldedLength(index: number): number; replaceOne(cell: ICellViewModel, range: Range, text: string): Promise; - replaceAll(matches: CellFindMatch[], texts: string[]): Promise; + replaceAll(matches: CellFindMatchWithIndex[], texts: string[]): Promise; } //#endregion @@ -701,21 +701,24 @@ export interface IActiveNotebookEditorDelegate extends INotebookEditorDelegate { getNextVisibleCellIndex(index: number): number; } -export interface OutputFindMatch { +export interface CellWebviewFindMatch { readonly index: number; } +export type CellContentFindMatch = FindMatch; + export interface CellFindMatch { cell: ICellViewModel; - matches: (FindMatch | OutputFindMatch)[]; - modelMatchCount: number; + contentMatches: CellContentFindMatch[]; } export interface CellFindMatchWithIndex { cell: ICellViewModel; index: number; - matches: (FindMatch | OutputFindMatch)[]; - modelMatchCount: number; + length: number; + getMatch(index: number): FindMatch | CellWebviewFindMatch; + contentMatches: FindMatch[]; + webviewMatches: CellWebviewFindMatch[]; } export enum CellEditState { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index feacb3e2174..57b162cb2f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -84,6 +84,7 @@ import { BaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/vie import { ILogService } from 'vs/platform/log/common/log'; import { FloatingClickMenu } from 'vs/workbench/browser/codeeditor'; import { IDimension } from 'vs/editor/common/core/dimension'; +import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; const $ = DOM.$; @@ -2395,7 +2396,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return []; } - const findMatches = this._notebookViewModel.find(query, options).filter(match => match.matches.length > 0); + const findMatches = this._notebookViewModel.find(query, options).filter(match => match.length > 0); if (!options.includeMarkupPreview && !options.includeOutput) { this._webview?.findStop(); @@ -2436,14 +2437,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const exisitingMatch = matchMap[match.cellId]; if (exisitingMatch) { - exisitingMatch.matches.push(match); + exisitingMatch.webviewMatches.push(match); } else { - matchMap[match.cellId] = { - cell: this._notebookViewModel!.viewCells.find(cell => cell.id === match.cellId)! as CellViewModel, - index: this._notebookViewModel!.viewCells.findIndex(cell => cell.id === match.cellId)!, - matches: [match], - modelMatchCount: 0 - }; + + matchMap[match.cellId] = new CellFindMatchModel( + this._notebookViewModel!.viewCells.find(cell => cell.id === match.cellId)!, + this._notebookViewModel!.viewCells.findIndex(cell => cell.id === match.cellId)!, + [], + [match] + ); } }); } @@ -2451,12 +2453,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const ret: CellFindMatchWithIndex[] = []; this._notebookViewModel.viewCells.forEach((cell, index) => { if (matchMap[cell.id]) { - ret.push({ - cell: cell as CellViewModel, - index: index, - matches: matchMap[cell.id].matches, - modelMatchCount: matchMap[cell.id].modelMatchCount - }); + ret.push(new CellFindMatchModel(cell, index, matchMap[cell.id].contentMatches, matchMap[cell.id].webviewMatches)); } }); 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 28e16255397..3958ddab7ef 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -1019,12 +1019,14 @@ async function webviewPreloads(ctx: PreloadContext) { break; } + // Markdown preview are rendered in a shadow DOM. if (options.includeMarkup && selection.rangeCount > 0 && selection.getRangeAt(0).startContainer.nodeType === 1 && (selection.getRangeAt(0).startContainer as Element).classList.contains('markup')) { // markdown preview container const preview = (selection.anchorNode?.firstChild as Element); const root = preview.shadowRoot as ShadowRoot & { getSelection: () => Selection }; const shadowSelection = root?.getSelection ? root?.getSelection() : null; + // find the match in the shadow dom by checking the selection inside the shadow dom if (shadowSelection && shadowSelection.anchorNode) { matches.push({ type: 'preview', @@ -1037,6 +1039,7 @@ async function webviewPreloads(ctx: PreloadContext) { } } + // Outputs might be rendered inside a shadow DOM. if (options.includeOutput && selection.rangeCount > 0 && selection.getRangeAt(0).startContainer.nodeType === 1 && (selection.getRangeAt(0).startContainer as Element).classList.contains('output_container')) { // output container @@ -1056,11 +1059,12 @@ async function webviewPreloads(ctx: PreloadContext) { } } - const anchorNode = selection?.anchorNode?.parentElement; + const anchorNode = selection.anchorNode?.parentElement; if (anchorNode) { const lastEl: any = matches.length ? matches[matches.length - 1] : null; + // Optimization: avoid searching for the output container if (lastEl && lastEl.container.contains(anchorNode) && options.includeOutput) { matches.push({ type: lastEl.type, @@ -1068,10 +1072,11 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: lastEl.cellId, container: lastEl.container, isShadow: false, - originalRange: window.getSelection()!.getRangeAt(0) + originalRange: selection.getRangeAt(0) }); } else { + // Traverse up the DOM to find the container for (let node = anchorNode as Element | null; node; node = node.parentElement) { if (!(node instanceof Element)) { break; @@ -1087,7 +1092,7 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, container: node, isShadow: false, - originalRange: window.getSelection()!.getRangeAt(0) + originalRange: selection.getRangeAt(0) }); } break; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index 099e660c7cb..d3d0fc622be 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -476,8 +476,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod return { cell: this, - matches, - modelMatchCount: matches.length + contentMatches: matches }; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts index b48947e9f01..de5fa1f1709 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts @@ -285,8 +285,7 @@ export class MarkupCellViewModel extends BaseCellViewModel implements ICellViewM return { cell: this, - matches, - modelMatchCount: matches.length + contentMatches: matches }; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 8dcc07dddf2..4337c50d40b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -22,7 +22,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { CellEditState, CellFindMatch, CellFindMatchWithIndex, CellFoldingState, EditorFoldingStateDelegate, ICellViewModel, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, OutputFindMatch, ICellModelDecorations, ICellModelDeltaDecorations, IModelDecorationsChangeAccessor, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFindMatchWithIndex, CellFoldingState, EditorFoldingStateDelegate, ICellViewModel, INotebookDeltaCellStatusBarItems, INotebookDeltaDecoration, ICellModelDecorations, ICellModelDeltaDecorations, IModelDecorationsChangeAccessor, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; @@ -32,6 +32,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellKind, ICell, INotebookSearchOptions, ISelectionState, NotebookCellsChangeType, NotebookCellTextModelSplice, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { cellIndexesToRanges, cellRangesToIndexes, ICellRange, reduceCellRanges } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookLayoutInfo, NotebookMetadataChangedEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; +import { CellFindMatchModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; @@ -891,12 +892,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._viewCells.forEach((cell, index) => { const cellMatches = cell.startFind(value, options); if (cellMatches) { - matches.push({ - cell: cellMatches.cell, - index: index, - matches: cellMatches.matches, - modelMatchCount: cellMatches.matches.length - }); + matches.push(new CellFindMatchModel( + cellMatches.cell, + index, + cellMatches.contentMatches, + [] + )); } }); @@ -914,7 +915,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); } - async replaceAll(matches: CellFindMatch[], texts: string[]): Promise { + async replaceAll(matches: CellFindMatchWithIndex[], texts: string[]): Promise { if (!matches.length) { return; } @@ -923,14 +924,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._lastNotebookEditResource.push(matches[0].cell.uri); matches.forEach(match => { - match.matches.forEach((singleMatch, index) => { - if ((singleMatch as OutputFindMatch).index === undefined) { - textEdits.push({ - versionId: undefined, - textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] }, - resource: match.cell.uri - }); - } + match.contentMatches.forEach((singleMatch, index) => { + textEdits.push({ + versionId: undefined, + textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] }, + resource: match.cell.uri + }); }); }); 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 50aeeb9f290..60d80fcd37d 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 @@ -5,14 +5,14 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; -import { ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; +import { FindMatch, ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IConfigurationService, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; -import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; +import { CellFindMatchModel, FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; import { IActiveNotebookEditor, ICellModelDecorations, ICellModelDeltaDecorations } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl'; import { CellEditType, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -262,4 +262,30 @@ suite('Notebook Find', () => { assert.strictEqual(model.findMatches.length, 0); }); }); + + test('CellFindMatchModel', async function () { + await withTestNotebook( + [ + ['# header 1', 'markdown', CellKind.Markup, [], {}], + ['print(1)', 'typescript', CellKind.Code, [], {}], + ], + async (editor) => { + const mdCell = editor.cellAt(0); + const mdModel = new CellFindMatchModel(mdCell, 0, [], []); + assert.strictEqual(mdModel.length, 0); + + mdModel.contentMatches.push(new FindMatch(new Range(1, 1, 1, 2), [])); + assert.strictEqual(mdModel.length, 1); + mdModel.webviewMatches.push({ + index: 0 + }, { + index: 1 + }); + + assert.strictEqual(mdModel.length, 3); + assert.strictEqual(mdModel.getMatch(0), mdModel.contentMatches[0]); + assert.strictEqual(mdModel.getMatch(1), mdModel.webviewMatches[0]); + assert.strictEqual(mdModel.getMatch(2), mdModel.webviewMatches[1]); + }); + }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 986e38163d5..974031e8349 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -272,7 +272,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override get onDidChangeOptions() { return viewModel.onDidChangeOptions; } override get onDidChangeViewCells() { return viewModel.onDidChangeViewCells; } override async find(query: string, options: INotebookSearchOptions): Promise { - const findMatches = viewModel.find(query, options).filter(match => match.matches.length > 0); + const findMatches = viewModel.find(query, options).filter(match => match.length > 0); return findMatches; } override deltaCellDecorations() { return []; }