Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions src/vs/workbench/contrib/search/browser/notebookSearchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { INotebookExclusiveDocumentFilter, NotebookData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { INotebookSearchService } from 'vs/workbench/contrib/search/browser/notebookSearch';
import { IFileMatchWithCells, ICellMatch, CellSearchModel, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers';
import { IFileMatchWithCells, ICellMatch, CellSearchModel, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches, genericCellMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers';
import { IEditorResolverService, priorityToRank } from 'vs/workbench/services/editor/common/editorResolverService';
import { ITextQuery, IFileQuery, QueryType, ISearchProgressItem, ISearchComplete, ISearchConfigurationProperties, ISearchService } from 'vs/workbench/services/search/common/search';
import * as arrays from 'vs/base/common/arrays';
Expand Down Expand Up @@ -202,23 +202,33 @@ export class NotebookSearchService implements INotebookSearchService {
return;
}

const cells = deserializedNotebooks.get(uri)?.cells ?? (await this._notebookDataCache.getNotebookData(uri)).cells;
const notebook = deserializedNotebooks.get(uri) ?? (await this._notebookDataCache.getNotebookData(uri));
const cells = notebook.cells;

if (token.isCancellationRequested) {
return;
}

cells.forEach((cell, index) => {
const target = textQuery.contentPattern.pattern;
const cellModel = cell instanceof NotebookCellTextModel ? new CellSearchModel('', cell.textBuffer, uri, index) : new CellSearchModel(cell.source, undefined, uri, index);

const matches = cellModel.find(target);
if (matches.length > 0) {
const cellModel = cell instanceof NotebookCellTextModel ? new CellSearchModel('', cell.textBuffer, cell.outputs.flatMap(value => value.outputs), uri, index) : new CellSearchModel(cell.source, undefined, cell.outputs.flatMap(value => value.outputs), uri, index);

const inputMatches = cellModel.findInInputs(target);
const outputMatches = cellModel.findInOutputs(target);
const webviewResults = outputMatches
.flatMap(outputMatch =>
genericCellMatchesToTextSearchMatches(outputMatch.matches, outputMatch.textBuffer, cellModel))
.map((textMatch, index) => {
textMatch.webviewIndex = index;
return textMatch;
});

if (inputMatches.length > 0 || outputMatches.length > 0) {
const cellMatch: ICellMatch = {
cell: cellModel,
index: index,
contentResults: contentMatchesToTextSearchMatches(matches, cellModel),
webviewResults: []
contentResults: contentMatchesToTextSearchMatches(inputMatches, cellModel),
webviewResults
};
cellMatches.push(cellMatch);
}
Expand Down
92 changes: 71 additions & 21 deletions src/vs/workbench/contrib/search/browser/searchNotebookHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeText
import { SearchParams } from 'vs/editor/common/model/textModelSearch';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';

export interface IFileMatchWithCells extends IFileMatch {
cellResults: ICellMatch[];
Expand All @@ -29,6 +30,14 @@ export function isIFileMatchWithCells(object: IFileMatch): object is IFileMatchW
// to text search results

export function contentMatchesToTextSearchMatches(contentMatches: FindMatch[], cell: ICellViewModel | CellSearchModel): ITextSearchMatch[] {
return genericCellMatchesToTextSearchMatches(
contentMatches,
cell instanceof CellSearchModel ? cell.inputTextBuffer : cell.textBuffer,
cell
);
}

export function genericCellMatchesToTextSearchMatches(contentMatches: FindMatch[], buffer: IReadonlyTextBuffer, cell: ICellViewModel | CellSearchModel) {
let previousEndLine = -1;
const contextGroupings: FindMatch[][] = [];
let currentContextGrouping: FindMatch[] = [];
Expand All @@ -54,7 +63,7 @@ export function contentMatchesToTextSearchMatches(contentMatches: FindMatch[], c
const firstLine = grouping[0].range.startLineNumber;
const lastLine = grouping[grouping.length - 1].range.endLineNumber;
for (let i = firstLine; i <= lastLine; i++) {
lineTexts.push(cell.textBuffer.getLineContent(i));
lineTexts.push(buffer.getLineContent(i));
}
return new TextSearchMatch(
lineTexts.join('\n') + '\n',
Expand All @@ -79,9 +88,15 @@ export function webviewMatchesToTextSearchMatches(webviewMatches: CellWebviewFin

// experimental

interface RawOutputFindMatch {
textBuffer: IReadonlyTextBuffer;
matches: FindMatch[];
}

export const rawCellPrefix = 'rawCell#';
export class CellSearchModel extends Disposable {
constructor(readonly _source: string, private _textBuffer: IReadonlyTextBuffer | undefined, private _uri: URI, private _cellIndex: number) {
private _outputTextBuffers: IReadonlyTextBuffer[] | undefined = undefined;
constructor(readonly _source: string, private _inputTextBuffer: IReadonlyTextBuffer | undefined, private _outputs: IOutputItemDto[], private _uri: URI, private _cellIndex: number) {
super();
}

Expand All @@ -93,40 +108,75 @@ export class CellSearchModel extends Disposable {
return this._uri;
}

public getFullModelRange(): Range {
const lineCount = this.textBuffer.getLineCount();
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
private _getFullModelRange(buffer: IReadonlyTextBuffer): Range {
const lineCount = buffer.getLineCount();
return new Range(1, 1, lineCount, this._getLineMaxColumn(buffer, lineCount));
}

public getLineMaxColumn(lineNumber: number): number {
if (lineNumber < 1 || lineNumber > this.textBuffer.getLineCount()) {
private _getLineMaxColumn(buffer: IReadonlyTextBuffer, lineNumber: number): number {
if (lineNumber < 1 || lineNumber > buffer.getLineCount()) {
throw new Error('Illegal value for lineNumber');
}
return this.textBuffer.getLineLength(lineNumber) + 1;
return buffer.getLineLength(lineNumber) + 1;
}

get textBuffer() {
if (this._textBuffer) {
return this._textBuffer;
get inputTextBuffer(): IReadonlyTextBuffer {
if (!this._inputTextBuffer) {
const builder = new PieceTreeTextBufferBuilder();
builder.acceptChunk(this._source);
const bufferFactory = builder.finish(true);
const { textBuffer, disposable } = bufferFactory.create(DefaultEndOfLine.LF);
this._inputTextBuffer = textBuffer;
this._register(disposable);
}

const builder = new PieceTreeTextBufferBuilder();
builder.acceptChunk(this._source);
const bufferFactory = builder.finish(true);
const { textBuffer, disposable } = bufferFactory.create(DefaultEndOfLine.LF);
this._textBuffer = textBuffer;
this._register(disposable);
return this._inputTextBuffer;
}

get outputTextBuffers(): IReadonlyTextBuffer[] {
if (!this._outputTextBuffers) {
this._outputTextBuffers = this._outputs.map((output) => {
const builder = new PieceTreeTextBufferBuilder();
builder.acceptChunk(output.data.toString());
const bufferFactory = builder.finish(true);
const { textBuffer, disposable } = bufferFactory.create(DefaultEndOfLine.LF);
this._register(disposable);
return textBuffer;
});
}
return this._outputTextBuffers;
}

return this._textBuffer;
findInInputs(target: string): FindMatch[] {
const searchParams = new SearchParams(target, false, false, null);
const searchData = searchParams.parseSearchRequest();
if (!searchData) {
return [];
}
const fullInputRange = this._getFullModelRange(this.inputTextBuffer);
return this.inputTextBuffer.findMatchesLineByLine(fullInputRange, searchData, true, 5000);
}

find(target: string): FindMatch[] {
findInOutputs(target: string): RawOutputFindMatch[] {
const searchParams = new SearchParams(target, false, false, null);
const searchData = searchParams.parseSearchRequest();
if (!searchData) {
return [];
}
const fullRange = this.getFullModelRange();
return this.textBuffer.findMatchesLineByLine(fullRange, searchData, true, 5000);
return this.outputTextBuffers.map(buffer => {
const matches = buffer.findMatchesLineByLine(
this._getFullModelRange(buffer),
searchData,
true,
5000
);
if (matches.length === 0) {
return undefined;
}
return {
textBuffer: buffer,
matches
};
}).filter((item): item is RawOutputFindMatch => !!item);
}
}