提交 de70176d 编写于 作者: R Rob Lourens

Fix #57186, Fix #31551 - implement previewOptions for text search API

上级 3d69f453
......@@ -67,7 +67,7 @@ export class RipgrepTextSearchEngine {
});
let gotResult = false;
this.ripgrepParser = new RipgrepParser(MAX_TEXT_RESULTS, cwd);
this.ripgrepParser = new RipgrepParser(MAX_TEXT_RESULTS, cwd, options.previewOptions);
this.ripgrepParser.on('result', (match: vscode.TextSearchResult) => {
gotResult = true;
progress.report(match);
......@@ -160,7 +160,7 @@ export class RipgrepParser extends EventEmitter {
private numResults = 0;
constructor(private maxResults: number, private rootFolder: string) {
constructor(private maxResults: number, private rootFolder: string, private previewOptions?: vscode.TextSearchPreviewOptions) {
super();
this.stringDecoder = new StringDecoder();
}
......@@ -293,12 +293,22 @@ export class RipgrepParser extends EventEmitter {
lineMatches
.map(range => {
let trimmedPreview = preview;
let trimmedPreviewRange = range;
if (this.previewOptions) {
const previewStart = Math.max(range.start.character - this.previewOptions.leadingChars, 0);
trimmedPreview = preview.substr(previewStart, this.previewOptions.totalChars - previewStart);
if (previewStart > 0) {
trimmedPreviewRange = new vscode.Range(0, range.start.character - previewStart, 0, range.end.character - previewStart);
}
}
return <vscode.TextSearchResult>{
uri: vscode.Uri.file(path.join(this.rootFolder, this.currentFile)),
range,
preview: {
text: preview,
match: new vscode.Range(0, range.start.character, 0, range.end.character)
text: trimmedPreview,
match: trimmedPreviewRange || new vscode.Range(0, range.start.character, 0, range.end.character)
}
};
})
......
......@@ -85,6 +85,7 @@ export interface ICommonQueryOptions<U> {
disregardExcludeSettings?: boolean;
ignoreSymlinks?: boolean;
maxFileSize?: number;
previewOptions?: ITextSearchPreviewOptions;
}
export interface IQueryOptions extends ICommonQueryOptions<uri> {
......@@ -132,15 +133,33 @@ export interface IPatternInfo {
export interface IFileMatch<U extends UriComponents = uri> {
resource?: U;
lineMatches?: ILineMatch[];
matches?: ITextSearchResult[];
}
export type IRawFileMatch2 = IFileMatch<UriComponents>;
export interface ILineMatch {
preview: string;
lineNumber: number;
offsetAndLengths: number[][];
export interface ITextSearchPreviewOptions {
maxLines: number;
leadingChars: number;
totalChars: number;
}
export interface ISearchRange {
readonly startLineNumber: number;
readonly startColumn: number;
readonly endLineNumber: number;
readonly endColumn: number;
}
export interface ITextSearchResultPreview {
text: string;
match: ISearchRange;
}
export interface ITextSearchResult {
uri?: uri;
range: ISearchRange;
preview: ITextSearchResultPreview;
}
export interface IProgress {
......@@ -204,18 +223,47 @@ export interface IFileIndexProviderStats {
filesWalked: number;
}
// ---- very simple implementation of the search model --------------------
export class FileMatch implements IFileMatch {
public lineMatches: LineMatch[] = [];
public matches: ITextSearchResult[] = [];
constructor(public resource: uri) {
// empty
}
}
export class LineMatch implements ILineMatch {
constructor(public preview: string, public lineNumber: number, public offsetAndLengths: number[][]) {
// empty
export class TextSearchResult implements ITextSearchResult {
range: ISearchRange;
preview: ITextSearchResultPreview;
constructor(fullLine: string, range: ISearchRange, previewOptions?: ITextSearchPreviewOptions) {
this.range = range;
if (previewOptions) {
const previewStart = Math.max(range.startColumn - previewOptions.leadingChars, 0);
const previewEnd = Math.max(previewOptions.totalChars + previewStart, range.endColumn);
this.preview = {
text: fullLine.substring(previewStart, previewEnd),
match: new OneLineRange(0, range.startColumn - previewStart, range.endColumn - previewStart)
};
} else {
this.preview = {
text: fullLine,
match: new OneLineRange(0, range.startColumn, range.endColumn)
};
}
}
}
export class OneLineRange implements ISearchRange {
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
constructor(lineNumber: number, startColumn: number, endColumn: number) {
this.startLineNumber = lineNumber;
this.startColumn = startColumn;
this.endLineNumber = lineNumber;
this.endColumn = endColumn;
}
}
......
......@@ -77,6 +77,12 @@ declare module 'vscode' {
followSymlinks: boolean;
}
export interface TextSearchPreviewOptions {
maxLines: number;
leadingChars: number;
totalChars: number;
}
/**
* Options that apply to text search.
*/
......@@ -86,10 +92,7 @@ declare module 'vscode' {
*/
maxResults: number;
/**
* TODO@roblou - total length? # of context lines? leading and trailing # of chars?
*/
previewOptions?: any;
previewOptions?: TextSearchPreviewOptions;
/**
* Exclude files larger than `maxFileSize` in bytes.
......
......@@ -78,7 +78,7 @@ class SearchOperation {
addMatch(match: IFileMatch): void {
if (this.matches.has(match.resource.toString())) {
// Merge with previous IFileMatches
this.matches.get(match.resource.toString()).lineMatches.push(...match.lineMatches);
this.matches.get(match.resource.toString()).matches.push(...match.matches);
} else {
this.matches.set(match.resource.toString(), match);
}
......@@ -149,10 +149,10 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
const searchOp = this._searches.get(session);
dataOrUri.forEach(result => {
if ((<IRawFileMatch2>result).lineMatches) {
if ((<IRawFileMatch2>result).matches) {
searchOp.addMatch({
resource: URI.revive((<IRawFileMatch2>result).resource),
lineMatches: (<IRawFileMatch2>result).lineMatches
matches: (<IRawFileMatch2>result).matches
});
} else {
searchOp.addMatch({
......
......@@ -181,7 +181,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
const query = queryBuilder.text(pattern, folders, options);
const onProgress = (p: ISearchProgressItem) => {
if (p.lineMatches) {
if (p.matches) {
this._proxy.$handleTextSearchResult(p, requestId);
}
};
......
......@@ -5,20 +5,20 @@
'use strict';
import * as path from 'path';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import * as glob from 'vs/base/common/glob';
import { toDisposable } from 'vs/base/common/lifecycle';
import * as resources from 'vs/base/common/resources';
import { StopWatch } from 'vs/base/common/stopwatch';
import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import * as extfs from 'vs/base/node/extfs';
import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, IFileSearchProviderStats } from 'vs/platform/search/common/search';
import { IFileMatch, IFileSearchProviderStats, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, ITextSearchResult } from 'vs/platform/search/common/search';
import { FileIndexSearchManager, IDirectoryEntry, IDirectoryTree, IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/api/node/extHostSearch.fileIndex';
import * as vscode from 'vscode';
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
import { toDisposable } from 'vs/base/common/lifecycle';
import { IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider, IDirectoryTree, IDirectoryEntry, FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex';
import { StopWatch } from 'vs/base/common/stopwatch';
import { isPromiseCanceledError } from 'vs/base/common/errors';
export interface ISchemeTransformer {
transformOutgoing(scheme: string): string;
......@@ -172,22 +172,16 @@ class TextSearchResultsCollector {
if (!this._currentFileMatch) {
this._currentFileMatch = {
resource: data.uri,
lineMatches: []
matches: []
};
}
// TODO@roblou - line text is sent for every match
const matchRange = data.preview.match;
this._currentFileMatch.lineMatches.push({
lineNumber: data.range.start.line,
preview: data.preview.text,
offsetAndLengths: [[matchRange.start.character, matchRange.end.character - matchRange.start.character]]
});
this._currentFileMatch.matches.push(extensionResultToFrontendResult(data));
}
private pushToCollector(): void {
const size = this._currentFileMatch ?
this._currentFileMatch.lineMatches.reduce((acc, match) => acc + match.offsetAndLengths.length, 0) :
this._currentFileMatch.matches.length :
0;
this._batchedCollector.addItem(this._currentFileMatch, size);
}
......@@ -202,6 +196,26 @@ class TextSearchResultsCollector {
}
}
function extensionResultToFrontendResult(data: vscode.TextSearchResult): ITextSearchResult {
return {
preview: {
match: {
startLineNumber: data.preview.match.start.line,
startColumn: data.preview.match.start.character,
endLineNumber: data.preview.match.end.line,
endColumn: data.preview.match.end.character
},
text: data.preview.text
},
range: {
startLineNumber: data.range.start.line,
startColumn: data.range.start.character,
endLineNumber: data.range.end.line,
endColumn: data.range.end.character
}
};
}
/**
* Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every
* set of items collected.
......@@ -414,7 +428,8 @@ class TextSearchEngine {
followSymlinks: !this.config.ignoreSymlinks,
encoding: this.config.fileEncoding,
maxFileSize: this.config.maxFileSize,
maxResults: this.config.maxResults
maxResults: this.config.maxResults,
previewOptions: this.config.previewOptions
};
}
}
......
......@@ -108,6 +108,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel {
private readonly selectCurrentMatchEmitter: Emitter<string>;
private delayedRefresh: Delayer<void>;
private changedWhileHidden: boolean;
private isWide: boolean;
private searchWithoutFolderMessageBuilder: Builder;
......@@ -826,8 +827,10 @@ export class SearchView extends Viewlet implements IViewlet, IPanel {
}
if (this.size.width >= SearchView.WIDE_VIEW_SIZE) {
this.isWide = true;
dom.addClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
} else {
this.isWide = false;
dom.removeClass(this.getContainer(), SearchView.WIDE_CLASS_NAME);
}
......@@ -1081,7 +1084,12 @@ export class SearchView extends Viewlet implements IViewlet, IPanel {
disregardIgnoreFiles: !useExcludesAndIgnoreFiles,
disregardExcludeSettings: !useExcludesAndIgnoreFiles,
excludePattern,
includePattern
includePattern,
previewOptions: {
leadingChars: 5,
maxLines: 1,
totalChars: this.isWide ? 1000 : 100
}
};
const folderResources = this.contextService.getWorkspace().folders;
......
......@@ -95,7 +95,8 @@ export class QueryBuilder {
useRipgrep,
disregardIgnoreFiles: options.disregardIgnoreFiles || !useIgnoreFiles,
disregardExcludeSettings: options.disregardExcludeSettings,
ignoreSymlinks
ignoreSymlinks,
previewOptions: options.previewOptions
};
// Filter extraFileResources against global include/exclude patterns - they are already expected to not belong to a workspace
......
......@@ -3,39 +3,50 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as objects from 'vs/base/common/objects';
import * as strings from 'vs/base/common/strings';
import * as errors from 'vs/base/common/errors';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { anyEvent, Emitter, Event, fromPromise, stopwatch } from 'vs/base/common/event';
import { getBaseLabel } from 'vs/base/common/labels';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap, TernarySearchTree, values } from 'vs/base/common/map';
import * as objects from 'vs/base/common/objects';
import URI from 'vs/base/common/uri';
import { values, ResourceMap, TernarySearchTree } from 'vs/base/common/map';
import { Event, Emitter, fromPromise, stopwatch, anyEvent } from 'vs/base/common/event';
import { ISearchService, ISearchProgressItem, ISearchComplete, ISearchQuery, IPatternInfo, IFileMatch, ITextSearchStats } from 'vs/platform/search/common/search';
import { ReplacePattern } from 'vs/platform/search/common/replace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TPromise } from 'vs/base/common/winjs.base';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, FindMatch } from 'vs/editor/common/model';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { FindMatch, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ReplacePattern } from 'vs/platform/search/common/replace';
import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, TextSearchResult } from 'vs/platform/search/common/search';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { getBaseLabel } from 'vs/base/common/labels';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
export class Match {
private _lineText: string;
private _id: string;
private _range: Range;
private _previewText: string;
private _rangeInPreviewText: Range;
constructor(private _parent: FileMatch, text: string, lineNumber: number, offset: number, length: number) {
this._lineText = text;
this._range = new Range(1 + lineNumber, 1 + offset, 1 + lineNumber, 1 + offset + length);
this._id = this._parent.id() + '>' + lineNumber + '>' + offset + this.getMatchString();
constructor(private _parent: FileMatch, _result: ITextSearchResult) {
this._range = new Range(
_result.range.startLineNumber + 1,
_result.range.startColumn + 1,
_result.range.endLineNumber + 1,
_result.range.endColumn + 1);
this._rangeInPreviewText = new Range(
_result.preview.match.startLineNumber + 1,
_result.preview.match.startColumn + 1,
_result.preview.match.endLineNumber + 1,
_result.preview.match.endColumn + 1);
this._previewText = _result.preview.text;
this._id = this._parent.id() + '>' + this._range + this.getMatchString();
}
public id(): string {
......@@ -47,7 +58,7 @@ export class Match {
}
public text(): string {
return this._lineText;
return this._previewText;
}
public range(): Range {
......@@ -55,11 +66,9 @@ export class Match {
}
public preview(): { before: string; inside: string; after: string; } {
let before = this._lineText.substring(0, this._range.startColumn - 1),
const before = this._previewText.substring(0, this._rangeInPreviewText.startColumn - 1),
inside = this.getMatchString(),
after = this._lineText.substring(this._range.endColumn - 1, Math.min(this._range.endColumn + 150, this._lineText.length));
before = strings.lcut(before, 26);
after = this._previewText.substring(this._rangeInPreviewText.endColumn - 1);
return {
before,
......@@ -75,7 +84,7 @@ export class Match {
// If match string is not matching then regex pattern has a lookahead expression
if (replaceString === null) {
replaceString = searchModel.replacePattern.getReplaceString(matchString + this._lineText.substring(this._range.endColumn - 1));
replaceString = searchModel.replacePattern.getReplaceString(matchString + this._previewText.substring(this._rangeInPreviewText.endColumn - 1));
}
// Match string is still not matching. Could be unsupported matches (multi-line).
......@@ -87,7 +96,7 @@ export class Match {
}
public getMatchString(): string {
return this._lineText.substring(this._range.startColumn - 1, this._range.endColumn - 1);
return this._previewText.substring(this._rangeInPreviewText.startColumn - 1, this._rangeInPreviewText.endColumn - 1);
}
}
......@@ -134,7 +143,7 @@ export class FileMatch extends Disposable {
private _updateScheduler: RunOnceScheduler;
private _modelDecorations: string[] = [];
constructor(private _query: IPatternInfo, private _maxResults: number, private _parent: FolderMatch, private rawMatch: IFileMatch,
constructor(private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions, private _maxResults: number, private _parent: FolderMatch, private rawMatch: IFileMatch,
@IModelService private modelService: IModelService, @IReplaceService private replaceService: IReplaceService) {
super();
this._resource = this.rawMatch.resource;
......@@ -152,11 +161,9 @@ export class FileMatch extends Disposable {
this.bindModel(model);
this.updateMatchesForModel();
} else {
this.rawMatch.lineMatches.forEach((rawLineMatch) => {
rawLineMatch.offsetAndLengths.forEach(offsetAndLength => {
let match = new Match(this, rawLineMatch.preview, rawLineMatch.lineNumber, offsetAndLength[0], offsetAndLength[1]);
this.add(match);
});
this.rawMatch.matches.forEach((rawLineMatch) => {
let match = new Match(this, rawLineMatch);
this.add(match);
});
}
}
......@@ -222,7 +229,12 @@ export class FileMatch extends Disposable {
private updateMatches(matches: FindMatch[], modelChange: boolean) {
matches.forEach(m => {
let match = new Match(this, this._model.getLineContent(m.range.startLineNumber), m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.endColumn - m.range.startColumn);
const textSearchResult = new TextSearchResult(
this._model.getLineContent(m.range.startLineNumber),
new Range(m.range.startLineNumber - 1, m.range.startColumn - 1, m.range.startLineNumber - 1, m.range.endColumn),
this._previewOptions);
const match = new Match(this, textSearchResult);
if (!this._removedMatches.has(match.id())) {
this.add(match);
if (this.isMatchSelected(match)) {
......@@ -392,16 +404,16 @@ export class FolderMatch extends Disposable {
}
public add(raw: IFileMatch[], silent: boolean): void {
let changed: FileMatch[] = [];
const changed: FileMatch[] = [];
raw.forEach((rawFileMatch) => {
if (this._fileMatches.has(rawFileMatch.resource)) {
this._fileMatches.get(rawFileMatch.resource).dispose();
}
let fileMatch = this.instantiationService.createInstance(FileMatch, this._query.contentPattern, this._query.maxResults, this, rawFileMatch);
const fileMatch = this.instantiationService.createInstance(FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch);
this.doAdd(fileMatch);
changed.push(fileMatch);
let disposable = fileMatch.onChange(() => this.onFileChange(fileMatch));
const disposable = fileMatch.onChange(() => this.onFileChange(fileMatch));
fileMatch.onDispose(() => disposable.dispose());
});
if (!silent && changed.length) {
......
......@@ -129,13 +129,26 @@ suite('Search Actions', () => {
function aFileMatch(): FileMatch {
let rawMatch: IFileMatch = {
resource: URI.file('somepath' + ++counter),
lineMatches: []
matches: []
};
return instantiationService.createInstance(FileMatch, null, null, null, rawMatch);
return instantiationService.createInstance(FileMatch, null, null, null, null, rawMatch);
}
function aMatch(fileMatch: FileMatch): Match {
let match = new Match(fileMatch, 'some match', ++counter, 0, 2);
const line = ++counter;
const range = {
startLineNumber: line,
startColumn: 0,
endLineNumber: line,
endColumn: 2
};
let match = new Match(fileMatch, {
preview: {
text: 'some match',
match: range
},
range
});
fileMatch.add(match);
return match;
}
......
......@@ -9,7 +9,7 @@ import uri from 'vs/base/common/uri';
import { Match, FileMatch, SearchResult } from 'vs/workbench/parts/search/common/searchModel';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { SearchDataSource, SearchSorter } from 'vs/workbench/parts/search/browser/searchResultsView';
import { IFileMatch, ILineMatch } from 'vs/platform/search/common/search';
import { IFileMatch, TextSearchResult, OneLineRange, ITextSearchResult } from 'vs/platform/search/common/search';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
......@@ -31,9 +31,22 @@ suite('Search - Viewlet', () => {
let ds = instantiation.createInstance(SearchDataSource);
let result: SearchResult = instantiation.createInstance(SearchResult, null);
result.query = { type: 1, folderQueries: [{ folder: uri.parse('file://c:/') }] };
const range = {
startLineNumber: 1,
startColumn: 0,
endLineNumber: 1,
endColumn: 1
};
result.add([{
resource: uri.parse('file:///c:/foo'),
lineMatches: [{ lineNumber: 1, preview: 'bar', offsetAndLengths: [[0, 1]] }]
matches: [{
preview: {
text: 'bar',
match: range
},
range
}]
}]);
let fileMatch = result.matches()[0];
......@@ -41,7 +54,7 @@ suite('Search - Viewlet', () => {
assert.equal(ds.getId(null, result), 'root');
assert.equal(ds.getId(null, fileMatch), 'file:///c%3A/foo');
assert.equal(ds.getId(null, lineMatch), 'file:///c%3A/foo>1>0b');
assert.equal(ds.getId(null, lineMatch), 'file:///c%3A/foo>[2,1 -> 2,2]b');
assert(!ds.hasChildren(null, 'foo'));
assert(ds.hasChildren(null, result));
......@@ -53,9 +66,9 @@ suite('Search - Viewlet', () => {
let fileMatch1 = aFileMatch('C:\\foo');
let fileMatch2 = aFileMatch('C:\\with\\path');
let fileMatch3 = aFileMatch('C:\\with\\path\\foo');
let lineMatch1 = new Match(fileMatch1, 'bar', 1, 1, 1);
let lineMatch2 = new Match(fileMatch1, 'bar', 2, 1, 1);
let lineMatch3 = new Match(fileMatch1, 'bar', 2, 1, 1);
let lineMatch1 = new Match(fileMatch1, new TextSearchResult('bar', new OneLineRange(0, 1, 1)));
let lineMatch2 = new Match(fileMatch1, new TextSearchResult('bar', new OneLineRange(2, 1, 1)));
let lineMatch3 = new Match(fileMatch1, new TextSearchResult('bar', new OneLineRange(2, 1, 1)));
let s = new SearchSorter();
......@@ -69,12 +82,12 @@ suite('Search - Viewlet', () => {
assert(s.compare(null, lineMatch2, lineMatch3) === 0);
});
function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ILineMatch[]): FileMatch {
function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ITextSearchResult[]): FileMatch {
let rawMatch: IFileMatch = {
resource: uri.file('C:\\' + path),
lineMatches: lineMatches
matches: lineMatches
};
return instantiation.createInstance(FileMatch, null, null, searchResult, rawMatch);
return instantiation.createInstance(FileMatch, null, null, null, searchResult, rawMatch);
}
function stubModelService(instantiationService: TestInstantiationService): IModelService {
......
......@@ -16,7 +16,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IFileMatch, IFileSearchStats, IFolderQuery, ILineMatch, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService } from 'vs/platform/search/common/search';
import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchResult, TextSearchResult, OneLineRange } from 'vs/platform/search/common/search';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { SearchModel } from 'vs/workbench/parts/search/common/searchModel';
......@@ -41,6 +41,7 @@ const nullEvent = new class {
}
};
const lineOneRange = new OneLineRange(1, 0, 1);
suite('SearchModel', () => {
......@@ -104,7 +105,11 @@ suite('SearchModel', () => {
}
test('Search Model: Search adds to results', async () => {
let results = [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]])), aRawMatch('file://c:/2', aLineMatch('preview 2'))];
let results = [
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11))),
aRawMatch('file://c:/2', new TextSearchResult('preview 2', lineOneRange))];
instantiationService.stub(ISearchService, searchServiceWithResults(results));
let testObject: SearchModel = instantiationService.createInstance(SearchModel);
......@@ -130,7 +135,12 @@ suite('SearchModel', () => {
test('Search Model: Search reports telemetry on search completed', async () => {
let target = instantiationService.spy(ITelemetryService, 'publicLog');
let results = [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]])), aRawMatch('file://c:/2', aLineMatch('preview 2'))];
let results = [
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11))),
aRawMatch('file://c:/2',
new TextSearchResult('preview 2', lineOneRange))];
instantiationService.stub(ISearchService, searchServiceWithResults(results));
let testObject: SearchModel = instantiationService.createInstance(SearchModel);
......@@ -168,7 +178,7 @@ suite('SearchModel', () => {
instantiationService.stub(ITelemetryService, 'publicLog', target1);
instantiationService.stub(ISearchService, searchServiceWithResults(
[aRawMatch('file://c:/1', aLineMatch('some preview'))],
[aRawMatch('file://c:/1', new TextSearchResult('some preview', lineOneRange))],
{ results: [], stats: testSearchStats }));
let testObject = instantiationService.createInstance(SearchModel);
......@@ -229,7 +239,12 @@ suite('SearchModel', () => {
});
test('Search Model: Search results are cleared during search', async () => {
let results = [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]])), aRawMatch('file://c:/2', aLineMatch('preview 2'))];
let results = [
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11))),
aRawMatch('file://c:/2',
new TextSearchResult('preview 2', lineOneRange))];
instantiationService.stub(ISearchService, searchServiceWithResults(results));
let testObject: SearchModel = instantiationService.createInstance(SearchModel);
await testObject.search({ contentPattern: { pattern: 'somestring' }, type: 1, folderQueries });
......@@ -254,7 +269,10 @@ suite('SearchModel', () => {
});
test('getReplaceString returns proper replace string for regExpressions', async () => {
let results = [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]]))];
let results = [
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11)))];
instantiationService.stub(ISearchService, searchServiceWithResults(results));
let testObject: SearchModel = instantiationService.createInstance(SearchModel);
......@@ -281,12 +299,8 @@ suite('SearchModel', () => {
assert.equal('helloe', match.replaceString);
});
function aRawMatch(resource: string, ...lineMatches: ILineMatch[]): IFileMatch {
return { resource: URI.parse(resource), lineMatches };
}
function aLineMatch(preview: string, lineNumber: number = 1, offsetAndLengths: number[][] = [[0, 1]]): ILineMatch {
return { preview, lineNumber, offsetAndLengths };
function aRawMatch(resource: string, ...matches: ITextSearchResult[]): IFileMatch {
return { resource: URI.parse(resource), matches };
}
function stub(arg1: any, arg2: any, arg3: any): sinon.SinonStub {
......
......@@ -9,7 +9,7 @@ import * as sinon from 'sinon';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Match, FileMatch, SearchResult, SearchModel } from 'vs/workbench/parts/search/common/searchModel';
import URI from 'vs/base/common/uri';
import { IFileMatch, ILineMatch } from 'vs/platform/search/common/search';
import { IFileMatch, TextSearchResult, OneLineRange, ITextSearchResult } from 'vs/platform/search/common/search';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { Range } from 'vs/editor/common/core/range';
......@@ -19,6 +19,8 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
const lineOneRange = new OneLineRange(1, 0, 1);
suite('SearchResult', () => {
let instantiationService: TestInstantiationService;
......@@ -33,21 +35,17 @@ suite('SearchResult', () => {
test('Line Match', function () {
let fileMatch = aFileMatch('folder/file.txt', null);
let lineMatch = new Match(fileMatch, 'foo bar', 1, 0, 3);
let lineMatch = new Match(fileMatch, new TextSearchResult('foo bar', new OneLineRange(1, 0, 3)));
assert.equal(lineMatch.text(), 'foo bar');
assert.equal(lineMatch.range().startLineNumber, 2);
assert.equal(lineMatch.range().endLineNumber, 2);
assert.equal(lineMatch.range().startColumn, 1);
assert.equal(lineMatch.range().endColumn, 4);
assert.equal('file:///folder/file.txt>1>0foo', lineMatch.id());
assert.equal('file:///folder/file.txt>[2,1 -> 2,4]foo', lineMatch.id());
});
test('Line Match - Remove', function () {
let fileMatch = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo bar',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}]);
let fileMatch = aFileMatch('folder/file.txt', aSearchResult(), new TextSearchResult('foo bar', new OneLineRange(1, 0, 3)));
let lineMatch = fileMatch.matches()[0];
fileMatch.remove(lineMatch);
assert.equal(fileMatch.matches().length, 0);
......@@ -66,15 +64,11 @@ suite('SearchResult', () => {
});
test('File Match: Select an existing match', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch(
'folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
testObject.setSelectedMatch(testObject.matches()[0]);
......@@ -82,15 +76,11 @@ suite('SearchResult', () => {
});
test('File Match: Select non existing match', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch(
'folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
let target = testObject.matches()[0];
testObject.remove(target);
......@@ -100,15 +90,11 @@ suite('SearchResult', () => {
});
test('File Match: isSelected return true for selected match', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch(
'folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
let target = testObject.matches()[0];
testObject.setSelectedMatch(target);
......@@ -116,32 +102,20 @@ suite('SearchResult', () => {
});
test('File Match: isSelected return false for un-selected match', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch('folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
testObject.setSelectedMatch(testObject.matches()[0]);
assert.ok(!testObject.isMatchSelected(testObject.matches()[1]));
});
test('File Match: unselect', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch(
'folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
testObject.setSelectedMatch(testObject.matches()[0]);
testObject.setSelectedMatch(null);
......@@ -149,16 +123,11 @@ suite('SearchResult', () => {
});
test('File Match: unselect when not selected', function () {
let testObject = aFileMatch('folder/file.txt', aSearchResult(), ...[{
preview: 'foo',
lineNumber: 1,
offsetAndLengths: [[0, 3]]
}, {
preview: 'bar',
lineNumber: 1,
offsetAndLengths: [[5, 3]]
}]);
let testObject = aFileMatch(
'folder/file.txt',
aSearchResult(),
new TextSearchResult('foo', new OneLineRange(1, 0, 3)),
new TextSearchResult('bar', new OneLineRange(1, 5, 3)));
testObject.setSelectedMatch(null);
assert.equal(null, testObject.getSelectedMatch());
......@@ -167,7 +136,7 @@ suite('SearchResult', () => {
test('Alle Drei Zusammen', function () {
let searchResult = instantiationService.createInstance(SearchResult, null);
let fileMatch = aFileMatch('far/boo', searchResult);
let lineMatch = new Match(fileMatch, 'foo bar', 1, 0, 3);
let lineMatch = new Match(fileMatch, new TextSearchResult('foo bar', new OneLineRange(1, 0, 3)));
assert(lineMatch.parent() === fileMatch);
assert(fileMatch.parent() === searchResult);
......@@ -175,7 +144,10 @@ suite('SearchResult', () => {
test('Adding a raw match will add a file match with line matches', function () {
let testObject = aSearchResult();
let target = [aRawMatch('file://c:/', aLineMatch('preview 1', 1, [[1, 3], [4, 7]]), aLineMatch('preview 2'))];
let target = [aRawMatch('file://c:/',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11)),
new TextSearchResult('preview 2', lineOneRange))];
testObject.add(target);
......@@ -200,7 +172,12 @@ suite('SearchResult', () => {
test('Adding multiple raw matches', function () {
let testObject = aSearchResult();
let target = [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]])), aRawMatch('file://c:/2', aLineMatch('preview 2'))];
let target = [
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', new OneLineRange(1, 1, 4)),
new TextSearchResult('preview 1', new OneLineRange(1, 4, 11))),
aRawMatch('file://c:/2',
new TextSearchResult('preview 2', lineOneRange))];
testObject.add(target);
......@@ -228,7 +205,11 @@ suite('SearchResult', () => {
let target2 = sinon.spy();
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1')), aRawMatch('file://c:/2', aLineMatch('preview 2'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange)),
aRawMatch('file://c:/2',
new TextSearchResult('preview 2', lineOneRange))]);
testObject.matches()[0].onDispose(target1);
testObject.matches()[1].onDispose(target2);
......@@ -243,7 +224,9 @@ suite('SearchResult', () => {
test('remove triggers change event', function () {
let target = sinon.spy();
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange))]);
let objectRoRemove = testObject.matches()[0];
testObject.onChange(target);
......@@ -256,7 +239,9 @@ suite('SearchResult', () => {
test('remove triggers change event', function () {
let target = sinon.spy();
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange))]);
let objectRoRemove = testObject.matches()[0];
testObject.onChange(target);
......@@ -268,7 +253,9 @@ suite('SearchResult', () => {
test('Removing all line matches and adding back will add file back to result', function () {
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange))]);
let target = testObject.matches()[0];
let matchToRemove = target.matches()[0];
target.remove(matchToRemove);
......@@ -283,7 +270,9 @@ suite('SearchResult', () => {
test('replace should remove the file match', function () {
instantiationService.stubPromise(IReplaceService, 'replace', null);
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange))]);
testObject.replace(testObject.matches()[0]);
......@@ -294,7 +283,9 @@ suite('SearchResult', () => {
let target = sinon.spy();
instantiationService.stubPromise(IReplaceService, 'replace', null);
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange))]);
testObject.onChange(target);
let objectRoRemove = testObject.matches()[0];
......@@ -307,7 +298,11 @@ suite('SearchResult', () => {
test('replaceAll should remove all file matches', function () {
instantiationService.stubPromise(IReplaceService, 'replace', null);
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1')), aRawMatch('file://c:/2', aLineMatch('preview 2'))]);
testObject.add([
aRawMatch('file://c:/1',
new TextSearchResult('preview 1', lineOneRange)),
aRawMatch('file://c:/2',
new TextSearchResult('preview 2', lineOneRange))]);
testObject.replaceAll(null);
......@@ -358,12 +353,12 @@ suite('SearchResult', () => {
// lineHasNoDecoration(oneModel, 2);
//});
function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ILineMatch[]): FileMatch {
function aFileMatch(path: string, searchResult?: SearchResult, ...lineMatches: ITextSearchResult[]): FileMatch {
let rawMatch: IFileMatch = {
resource: URI.file('/' + path),
lineMatches: lineMatches
matches: lineMatches
};
return instantiationService.createInstance(FileMatch, null, null, searchResult, rawMatch);
return instantiationService.createInstance(FileMatch, null, null, null, searchResult, rawMatch);
}
function aSearchResult(): SearchResult {
......@@ -372,12 +367,8 @@ suite('SearchResult', () => {
return searchModel.searchResult;
}
function aRawMatch(resource: string, ...lineMatches: ILineMatch[]): IFileMatch {
return { resource: URI.parse(resource), lineMatches };
}
function aLineMatch(preview: string, lineNumber: number = 1, offsetAndLengths: number[][] = [[0, 1]]): ILineMatch {
return { preview, lineNumber, offsetAndLengths };
function aRawMatch(resource: string, ...matches: ITextSearchResult[]): IFileMatch {
return { resource: URI.parse(resource), matches };
}
function stubModelService(instantiationService: TestInstantiationService): IModelService {
......
......@@ -16,9 +16,10 @@ import * as strings from 'vs/base/common/strings';
import { TPromise } from 'vs/base/common/winjs.base';
import * as encoding from 'vs/base/node/encoding';
import * as extfs from 'vs/base/node/extfs';
import { IProgress, ITextSearchStats } from 'vs/platform/search/common/search';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IProgress, ITextSearchPreviewOptions, ITextSearchStats, TextSearchResult } from 'vs/platform/search/common/search';
import { rgPath } from 'vscode-ripgrep';
import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, LineMatch, ISerializedSearchSuccess } from './search';
import { FileMatch, IFolderSearch, IRawSearch, ISerializedFileMatch, ISerializedSearchSuccess } from './search';
// If vscode-ripgrep is in an .asar file, then the binary is unpacked.
const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
......@@ -77,7 +78,7 @@ export class RipgrepEngine {
this.rgProc = cp.spawn(rgDiskPath, rgArgs.args, { cwd });
process.once('exit', this.killRgProcFn);
this.ripgrepParser = new RipgrepParser(this.config.maxResults, cwd, this.config.extraFiles);
this.ripgrepParser = new RipgrepParser(this.config.maxResults, cwd, this.config.extraFiles, this.config.previewOptions);
this.ripgrepParser.on('result', (match: ISerializedFileMatch) => {
if (this.postProcessExclusions) {
const handleResultP = (<TPromise<string>>this.postProcessExclusions(match.path, undefined, glob.hasSiblingPromiseFn(() => getSiblings(match.path))))
......@@ -197,7 +198,7 @@ export class RipgrepParser extends EventEmitter {
private numResults = 0;
constructor(private maxResults: number, private rootFolder: string, extraFiles?: string[]) {
constructor(private maxResults: number, private rootFolder: string, extraFiles?: string[], private previewOptions?: ITextSearchPreviewOptions) {
super();
this.stringDecoder = new StringDecoder();
......@@ -275,7 +276,6 @@ export class RipgrepParser extends EventEmitter {
text = strings.stripUTF8BOM(text);
}
const lineMatch = new LineMatch(text, lineNum);
if (!this.fileMatch) {
// When searching a single file and no folderQueries, rg does not print the file line, so create it here
const singleFile = this.extraSearchFiles[0];
......@@ -286,8 +286,6 @@ export class RipgrepParser extends EventEmitter {
this.fileMatch = this.getFileMatch(singleFile);
}
this.fileMatch.addMatch(lineMatch);
let lastMatchEndPos = 0;
let matchTextStartPos = -1;
......@@ -296,6 +294,7 @@ export class RipgrepParser extends EventEmitter {
let textRealIdx = 0;
let hitLimit = false;
const matchRanges: IRange[] = [];
const realTextParts: string[] = [];
for (let i = 0; i < text.length - (RipgrepParser.MATCH_END_MARKER.length - 1);) {
......@@ -311,7 +310,7 @@ export class RipgrepParser extends EventEmitter {
const chunk = text.slice(matchTextStartPos, i);
realTextParts.push(chunk);
if (!hitLimit) {
lineMatch.addMatch(matchTextStartRealIdx, textRealIdx - matchTextStartRealIdx);
matchRanges.push(new Range(lineNum, matchTextStartRealIdx, lineNum, textRealIdx));
}
matchTextStartPos = -1;
......@@ -336,7 +335,9 @@ export class RipgrepParser extends EventEmitter {
// Replace preview with version without color codes
const preview = realTextParts.join('');
lineMatch.preview = preview;
matchRanges
.map(r => new TextSearchResult(preview, r, this.previewOptions))
.forEach(m => this.fileMatch.addMatch(m));
if (hitLimit) {
this.cancel();
......
......@@ -5,11 +5,11 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { Event } from 'vs/base/common/event';
import { IExpression } from 'vs/base/common/glob';
import { IProgress, ILineMatch, IPatternInfo, IFileSearchStats, ISearchEngineStats, ITextSearchStats } from 'vs/platform/search/common/search';
import { TPromise } from 'vs/base/common/winjs.base';
import { IFileSearchStats, IPatternInfo, IProgress, ISearchEngineStats, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats } from 'vs/platform/search/common/search';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { Event } from 'vs/base/common/event';
export interface IFolderSearch {
folder: string;
......@@ -34,6 +34,7 @@ export interface IRawSearch {
maxFilesize?: number;
useRipgrep?: boolean;
disregardIgnoreFiles?: boolean;
previewOptions?: ITextSearchPreviewOptions;
}
export interface ITelemetryEvent {
......@@ -96,7 +97,7 @@ export function isSerializedSearchSuccess(arg: ISerializedSearchComplete): arg i
export interface ISerializedFileMatch {
path: string;
lineMatches?: ILineMatch[];
matches?: ITextSearchResult[];
numMatches?: number;
}
......@@ -107,56 +108,22 @@ export type IFileSearchProgressItem = IRawFileMatch | IRawFileMatch[] | IProgres
export class FileMatch implements ISerializedFileMatch {
path: string;
lineMatches: LineMatch[];
matches: ITextSearchResult[];
constructor(path: string) {
this.path = path;
this.lineMatches = [];
this.matches = [];
}
addMatch(lineMatch: LineMatch): void {
this.lineMatches.push(lineMatch);
addMatch(match: ITextSearchResult): void {
this.matches.push(match);
}
serialize(): ISerializedFileMatch {
let lineMatches: ILineMatch[] = [];
let numMatches = 0;
for (let i = 0; i < this.lineMatches.length; i++) {
numMatches += this.lineMatches[i].offsetAndLengths.length;
lineMatches.push(this.lineMatches[i].serialize());
}
return {
path: this.path,
lineMatches,
numMatches
matches: this.matches,
numMatches: this.matches.length
};
}
}
export class LineMatch implements ILineMatch {
preview: string;
lineNumber: number;
offsetAndLengths: number[][];
constructor(preview: string, lineNumber: number) {
this.preview = preview.replace(/(\r|\n)*$/, '');
this.lineNumber = lineNumber;
this.offsetAndLengths = [];
}
addMatch(offset: number, length: number): void {
this.offsetAndLengths.push([offset, length]);
}
serialize(): ILineMatch {
const result = {
preview: this.preview,
lineNumber: this.lineNumber,
offsetAndLengths: this.offsetAndLengths
};
return result;
}
}
\ No newline at end of file
......@@ -22,13 +22,14 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILogService } from 'vs/platform/log/common/log';
import { FileMatch, ICachedSearchStats, IFileMatch, IFolderQuery, IProgress, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, LineMatch, pathIncludedInQuery, QueryType, SearchProviderType, IFileSearchStats } from 'vs/platform/search/common/search';
import { FileMatch, ICachedSearchStats, IFileMatch, IFolderQuery, IProgress, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, pathIncludedInQuery, QueryType, SearchProviderType, IFileSearchStats, TextSearchResult } from 'vs/platform/search/common/search';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search';
import { ISearchChannel, SearchChannelClient } from './searchIpc';
import { Range } from 'vs/editor/common/core/range';
export class SearchService extends Disposable implements ISearchService {
public _serviceBrand: any;
......@@ -346,7 +347,10 @@ export class SearchService extends Disposable implements ISearchService {
localResults.set(resource, fileMatch);
matches.forEach((match) => {
fileMatch.lineMatches.push(new LineMatch(model.getLineContent(match.range.startLineNumber), match.range.startLineNumber - 1, [[match.range.startColumn - 1, match.range.endColumn - match.range.startColumn]]));
fileMatch.matches.push(new TextSearchResult(
model.getLineContent(match.range.startLineNumber),
new Range(match.range.startLineNumber - 1, match.range.startColumn - 1, match.range.startLineNumber - 1, match.range.endColumn),
query.previewOptions));
});
} else {
localResults.set(resource, null);
......@@ -458,7 +462,8 @@ export class DiskSearch implements ISearchResultProvider {
cacheKey: query.cacheKey,
useRipgrep: query.useRipgrep,
disregardIgnoreFiles: query.disregardIgnoreFiles,
ignoreSymlinks: query.ignoreSymlinks
ignoreSymlinks: query.ignoreSymlinks,
previewOptions: query.previewOptions
};
for (const q of existingFolders) {
......@@ -536,10 +541,8 @@ export class DiskSearch implements ISearchResultProvider {
private static createFileMatch(data: ISerializedFileMatch): FileMatch {
let fileMatch = new FileMatch(uri.file(data.path));
if (data.lineMatches) {
for (let j = 0; j < data.lineMatches.length; j++) {
fileMatch.lineMatches.push(new LineMatch(data.lineMatches[j].preview, data.lineMatches[j].lineNumber, data.lineMatches[j].offsetAndLengths));
}
if (data.matches) {
fileMatch.matches.push(...data.matches); // TODO why
}
return fileMatch;
}
......
......@@ -11,7 +11,7 @@ import { IProgress } from 'vs/platform/search/common/search';
import { FileWalker } from 'vs/workbench/services/search/node/fileSearch';
import { IRawSearch, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch } from './search';
import { ITextSearchWorkerProvider } from './textSearchWorkerProvider';
import { ISearchWorker } from './worker/searchWorkerIpc';
import { ISearchWorker, ISearchWorkerSearchArgs } from './worker/searchWorkerIpc';
export class Engine implements ISearchEngine<ISerializedFileMatch[]> {
......@@ -95,7 +95,7 @@ export class Engine implements ISearchEngine<ISerializedFileMatch[]> {
this.nextWorker = (this.nextWorker + 1) % this.workers.length;
const maxResults = this.config.maxResults && (this.config.maxResults - this.numResults);
const searchArgs = { absolutePaths: batch, maxResults, pattern: this.config.contentPattern, fileEncoding };
const searchArgs: ISearchWorkerSearchArgs = { absolutePaths: batch, maxResults, pattern: this.config.contentPattern, fileEncoding, previewOptions: this.config.previewOptions };
worker.search(searchArgs).then(result => {
if (!result || this.limitReached || this.isCanceled) {
return unwind(batchBytes);
......
......@@ -7,16 +7,17 @@
import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
gracefulFs.gracefulify(fs);
import { onUnexpectedError } from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
import { TPromise } from 'vs/base/common/winjs.base';
import { LineMatch, FileMatch } from '../search';
import { UTF16le, UTF16be, UTF8, UTF8_with_bom, encodingExists, decode, bomLength, detectEncodingFromBuffer } from 'vs/base/node/encoding';
import { bomLength, decode, detectEncodingFromBuffer, encodingExists, UTF16be, UTF16le, UTF8, UTF8_with_bom } from 'vs/base/node/encoding';
import { Range } from 'vs/editor/common/core/range';
import { ITextSearchPreviewOptions, TextSearchResult } from 'vs/platform/search/common/search';
import { FileMatch } from '../search';
import { ISearchWorker, ISearchWorkerSearchArgs, ISearchWorkerSearchResult } from './searchWorkerIpc';
gracefulFs.gracefulify(fs);
interface ReadLinesOptions {
bufferLength: number;
encoding: string;
......@@ -95,7 +96,7 @@ export class SearchWorkerEngine {
// Search in the given path, and when it's finished, search in the next path in absolutePaths
const startSearchInFile = (absolutePath: string): TPromise<void> => {
return this.searchInFile(absolutePath, contentPattern, fileEncoding, args.maxResults && (args.maxResults - result.numMatches)).then(fileResult => {
return this.searchInFile(absolutePath, contentPattern, fileEncoding, args.maxResults && (args.maxResults - result.numMatches), args.previewOptions).then(fileResult => {
// Finish early if search is canceled
if (this.isCanceled) {
return;
......@@ -124,13 +125,12 @@ export class SearchWorkerEngine {
this.isCanceled = true;
}
private searchInFile(absolutePath: string, contentPattern: RegExp, fileEncoding: string, maxResults?: number): TPromise<IFileSearchResult> {
private searchInFile(absolutePath: string, contentPattern: RegExp, fileEncoding: string, maxResults?: number, previewOptions?: ITextSearchPreviewOptions): TPromise<IFileSearchResult> {
let fileMatch: FileMatch = null;
let limitReached = false;
let numMatches = 0;
const perLineCallback = (line: string, lineNumber: number) => {
let lineMatch: LineMatch = null;
let match = contentPattern.exec(line);
// Record all matches into file result
......@@ -139,12 +139,8 @@ export class SearchWorkerEngine {
fileMatch = new FileMatch(absolutePath);
}
if (lineMatch === null) {
lineMatch = new LineMatch(line, lineNumber);
fileMatch.addMatch(lineMatch);
}
lineMatch.addMatch(match.index, match[0].length);
const lineMatch = new TextSearchResult(line, new Range(lineNumber, match.index, lineNumber, match.index + match[0].length), previewOptions);
fileMatch.addMatch(lineMatch);
numMatches++;
if (maxResults && numMatches >= maxResults) {
......
......@@ -8,7 +8,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/node/ipc';
import { ISerializedFileMatch } from '../search';
import { IPatternInfo } from 'vs/platform/search/common/search';
import { IPatternInfo, ITextSearchPreviewOptions } from 'vs/platform/search/common/search';
import { SearchWorker } from './searchWorker';
import { Event } from 'vs/base/common/event';
......@@ -17,6 +17,7 @@ export interface ISearchWorkerSearchArgs {
fileEncoding: string;
absolutePaths: string[];
maxResults?: number;
previewOptions?: ITextSearchPreviewOptions;
}
export interface ISearchWorkerSearchResult {
......
......@@ -5,16 +5,13 @@
'use strict';
import * as path from 'path';
import * as assert from 'assert';
import * as path from 'path';
import * as arrays from 'vs/base/common/arrays';
import * as platform from 'vs/base/common/platform';
import { RipgrepParser, getAbsoluteGlob, fixDriveC, fixRegexEndingPattern } from 'vs/workbench/services/search/node/ripgrepTextSearch';
import { fixDriveC, fixRegexEndingPattern, getAbsoluteGlob, RipgrepParser } from 'vs/workbench/services/search/node/ripgrepTextSearch';
import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search';
suite('RipgrepParser', () => {
const rootFolder = '/workspace';
const fileSectionEnd = '\n';
......@@ -76,16 +73,40 @@ suite('RipgrepParser', () => {
<ISerializedFileMatch>{
numMatches: 2,
path: path.join(rootFolder, 'a.txt'),
lineMatches: [
matches: [
{
lineNumber: 0,
preview: 'beforematchafter',
offsetAndLengths: [[6, 5]]
preview: {
match: {
endColumn: 11,
endLineNumber: 0,
startColumn: 6,
startLineNumber: 0,
},
text: 'beforematchafter'
},
range: {
endColumn: 11,
endLineNumber: 0,
startColumn: 6,
startLineNumber: 0,
}
},
{
lineNumber: 1,
preview: 'beforematchafter',
offsetAndLengths: [[6, 5]]
preview: {
match: {
endColumn: 11,
endLineNumber: 0,
startColumn: 6,
startLineNumber: 0,
},
text: 'beforematchafter'
},
range: {
endColumn: 11,
endLineNumber: 1,
startColumn: 6,
startLineNumber: 1,
}
}
]
});
......@@ -168,8 +189,9 @@ suite('RipgrepParser', () => {
const results = parseInput(inputBufs);
assert.equal(results.length, 1);
assert.equal(results[0].lineMatches.length, 1);
assert.deepEqual(results[0].lineMatches[0].offsetAndLengths, [[7, 5]]);
assert.equal(results[0].matches.length, 1);
assert.equal(results[0].matches[0].range.startColumn, 7);
assert.equal(results[0].matches[0].range.endColumn, 12);
}
});
});
......
......@@ -650,14 +650,15 @@ suite('ExtHostSearch', () => {
const actualTextSearchResults: vscode.TextSearchResult[] = [];
for (let fileMatch of actual) {
// Make relative
for (let lineMatch of fileMatch.lineMatches) {
for (let [offset, length] of lineMatch.offsetAndLengths) {
actualTextSearchResults.push({
preview: { text: lineMatch.preview, match: null },
range: new Range(lineMatch.lineNumber, offset, lineMatch.lineNumber, length + offset),
uri: fileMatch.resource
});
}
for (let lineMatch of fileMatch.matches) {
actualTextSearchResults.push({
preview: {
text: lineMatch.preview.text,
match: new Range(lineMatch.preview.match.startLineNumber, lineMatch.preview.match.startColumn, lineMatch.preview.match.endLineNumber, lineMatch.preview.match.endColumn)
},
range: new Range(lineMatch.range.startLineNumber, lineMatch.range.startColumn, lineMatch.range.endLineNumber, lineMatch.range.endColumn),
uri: fileMatch.resource
});
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册