提交 758a61e0 编写于 作者: S Sandeep Somavarapu

Implement #9010

上级 d926a2f0
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import {RunOnceScheduler} from 'vs/base/common/async'; import {RunOnceScheduler} from 'vs/base/common/async';
import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import * as strings from 'vs/base/common/strings'; import * as strings from 'vs/base/common/strings';
import {ReplacePattern} from 'vs/platform/search/common/replace';
import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand'; import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand';
import {Position} from 'vs/editor/common/core/position'; import {Position} from 'vs/editor/common/core/position';
import {Range} from 'vs/editor/common/core/range'; import {Range} from 'vs/editor/common/core/range';
...@@ -290,13 +291,8 @@ export class FindModelBoundToEditorModel { ...@@ -290,13 +291,8 @@ export class FindModelBoundToEditorModel {
} }
private getReplaceString(matchedString:string): string { private getReplaceString(matchedString:string): string {
if (!this._state.isRegex) { let replacePattern= new ReplacePattern(this._state.replaceString, {pattern: this._state.searchString, isRegExp: this._state.isRegex, isCaseSensitive: this._state.matchCase, isWordMatch: this._state.wholeWord});
return this._state.replaceString; return replacePattern.getReplaceString(matchedString);
}
let regexp = strings.createRegExp(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord, true);
// Parse the replace string to support that \t or \n mean the right thing
let parsedReplaceString = parseReplaceString(this._state.replaceString);
return matchedString.replace(regexp, parsedReplaceString);
} }
private _rangeIsMatch(range:Range): boolean { private _rangeIsMatch(range:Range): boolean {
...@@ -378,95 +374,4 @@ export class FindModelBoundToEditorModel { ...@@ -378,95 +374,4 @@ export class FindModelBoundToEditorModel {
this._ignoreModelContentChanged = false; this._ignoreModelContentChanged = false;
} }
} }
} }
\ No newline at end of file
const BACKSLASH_CHAR_CODE = '\\'.charCodeAt(0);
const DOLLAR_CHAR_CODE = '$'.charCodeAt(0);
const ZERO_CHAR_CODE = '0'.charCodeAt(0);
const n_CHAR_CODE = 'n'.charCodeAt(0);
const t_CHAR_CODE = 't'.charCodeAt(0);
/**
* \n => LF
* \t => TAB
* \\ => \
* $0 => $& (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter)
* everything else stays untouched
*/
export function parseReplaceString(input:string): string {
if (!input || input.length === 0) {
return input;
}
let substrFrom = 0, result = '';
for (let i = 0, len = input.length; i < len; i++) {
let chCode = input.charCodeAt(i);
if (chCode === BACKSLASH_CHAR_CODE) {
// move to next char
i++;
if (i >= len) {
// string ends with a \
break;
}
let nextChCode = input.charCodeAt(i);
let replaceWithCharacter: string = null;
switch (nextChCode) {
case BACKSLASH_CHAR_CODE:
// \\ => \
replaceWithCharacter = '\\';
break;
case n_CHAR_CODE:
// \n => LF
replaceWithCharacter = '\n';
break;
case t_CHAR_CODE:
// \t => TAB
replaceWithCharacter = '\t';
break;
}
if (replaceWithCharacter) {
result += input.substring(substrFrom, i - 1) + replaceWithCharacter;
substrFrom = i + 1;
}
}
if (chCode === DOLLAR_CHAR_CODE) {
// move to next char
i++;
if (i >= len) {
// string ends with a $
break;
}
let nextChCode = input.charCodeAt(i);
let replaceWithCharacter: string = null;
switch (nextChCode) {
case ZERO_CHAR_CODE:
// $0 => $&
replaceWithCharacter = '$&';
break;
}
if (replaceWithCharacter) {
result += input.substring(substrFrom, i - 1) + replaceWithCharacter;
substrFrom = i + 1;
}
}
}
if (substrFrom === 0) {
// no replacement occured
return input;
}
return result + input.substring(substrFrom);
}
...@@ -10,66 +10,12 @@ import {Position} from 'vs/editor/common/core/position'; ...@@ -10,66 +10,12 @@ import {Position} from 'vs/editor/common/core/position';
import {Selection} from 'vs/editor/common/core/selection'; import {Selection} from 'vs/editor/common/core/selection';
import {Range} from 'vs/editor/common/core/range'; import {Range} from 'vs/editor/common/core/range';
import {Handler, ICommonCodeEditor, IRange} from 'vs/editor/common/editorCommon'; import {Handler, ICommonCodeEditor, IRange} from 'vs/editor/common/editorCommon';
import {FindModelBoundToEditorModel, parseReplaceString} from 'vs/editor/contrib/find/common/findModel'; import {FindModelBoundToEditorModel} from 'vs/editor/contrib/find/common/findModel';
import {FindReplaceState} from 'vs/editor/contrib/find/common/findState'; import {FindReplaceState} from 'vs/editor/contrib/find/common/findState';
import {withMockCodeEditor} from 'vs/editor/test/common/mocks/mockCodeEditor'; import {withMockCodeEditor} from 'vs/editor/test/common/mocks/mockCodeEditor';
suite('FindModel', () => { suite('FindModel', () => {
test('parseFindWidgetString', () => {
let testParse = (input:string, expected:string) => {
let actual = parseReplaceString(input);
assert.equal(actual, expected);
let actual2 = parseReplaceString('hello' + input + 'hi');
assert.equal(actual2, 'hello' + expected + 'hi');
};
// no backslash => no treatment
testParse('hello', 'hello');
// \t => TAB
testParse('\\thello', '\thello');
// \n => LF
testParse('\\nhello', '\nhello');
// \\t => \t
testParse('\\\\thello', '\\thello');
// \\\t => \TAB
testParse('\\\\\\thello', '\\\thello');
// \\\\t => \\t
testParse('\\\\\\\\thello', '\\\\thello');
// \ at the end => no treatment
testParse('hello\\', 'hello\\');
// \ with unknown char => no treatment
testParse('hello\\x', 'hello\\x');
// \ with back reference => no treatment
testParse('hello\\0', 'hello\\0');
// $1 => no treatment
testParse('hello$1', 'hello$1');
// $2 => no treatment
testParse('hello$2', 'hello$2');
// $12 => no treatment
testParse('hello$12', 'hello$12');
// $$ => no treatment
testParse('hello$$', 'hello$$');
// $$0 => no treatment
testParse('hello$$0', 'hello$$0');
// $0 => $&
testParse('hello$0', 'hello$&');
testParse('hello$02', 'hello$&2');
});
function findTest(testName:string, callback:(editor:ICommonCodeEditor, cursor:Cursor)=>void): void { function findTest(testName:string, callback:(editor:ICommonCodeEditor, cursor:Cursor)=>void): void {
test(testName, () => { test(testName, () => {
withMockCodeEditor([ withMockCodeEditor([
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as strings from 'vs/base/common/strings';
import {IPatternInfo} from 'vs/platform/search/common/search';
const BACKSLASH_CHAR_CODE = '\\'.charCodeAt(0);
const DOLLAR_CHAR_CODE = '$'.charCodeAt(0);
const ZERO_CHAR_CODE = '0'.charCodeAt(0);
const ONE_CHAR_CODE = '1'.charCodeAt(0);
const NINE_CHAR_CODE = '9'.charCodeAt(0);
const BACK_TICK_CHAR_CODE = '`'.charCodeAt(0);
const SINGLE_QUOTE_CHAR_CODE = '`'.charCodeAt(0);
const n_CHAR_CODE = 'n'.charCodeAt(0);
const t_CHAR_CODE = 't'.charCodeAt(0);
export class ReplacePattern {
private _replacePattern: string;
private _searchRegExp: RegExp;
private _hasParameters: boolean= false;
constructor(private replaceString: string, private searchPatternInfo: IPatternInfo) {
this._replacePattern= replaceString;
if (searchPatternInfo.isRegExp) {
this._searchRegExp= strings.createRegExp(searchPatternInfo.pattern, searchPatternInfo.isRegExp, searchPatternInfo.isCaseSensitive, searchPatternInfo.isWordMatch, true);
this.parseReplaceString(replaceString);
}
}
public get hasParameters(): boolean {
return this._hasParameters;
}
public get pattern(): string {
return this._replacePattern;
}
public getReplaceString(matchedString: string): string {
if (this.hasParameters) {
return matchedString.replace(this._searchRegExp, this.pattern);
}
return this.pattern;
}
/**
* \n => LF
* \t => TAB
* \\ => \
* $0 => $& (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter)
* everything else stays untouched
*/
private parseReplaceString(replaceString: string): void {
if (!replaceString || replaceString.length === 0) {
return;
}
let substrFrom = 0, result = '';
for (let i = 0, len = replaceString.length; i < len; i++) {
let chCode = replaceString.charCodeAt(i);
if (chCode === BACKSLASH_CHAR_CODE) {
// move to next char
i++;
if (i >= len) {
// string ends with a \
break;
}
let nextChCode = replaceString.charCodeAt(i);
let replaceWithCharacter: string = null;
switch (nextChCode) {
case BACKSLASH_CHAR_CODE:
// \\ => \
replaceWithCharacter = '\\';
break;
case n_CHAR_CODE:
// \n => LF
replaceWithCharacter = '\n';
break;
case t_CHAR_CODE:
// \t => TAB
replaceWithCharacter = '\t';
break;
}
if (replaceWithCharacter) {
result += replaceString.substring(substrFrom, i - 1) + replaceWithCharacter;
substrFrom = i + 1;
}
}
if (chCode === DOLLAR_CHAR_CODE) {
// move to next char
i++;
if (i >= len) {
// string ends with a $
break;
}
let nextChCode = replaceString.charCodeAt(i);
let replaceWithCharacter: string = null;
switch (nextChCode) {
case ZERO_CHAR_CODE:
// $0 => $&
replaceWithCharacter = '$&';
this._hasParameters = true;
break;
case BACK_TICK_CHAR_CODE:
case SINGLE_QUOTE_CHAR_CODE:
this._hasParameters = true;
break;
default:
// check if it is a valid string parameter $n (0 <= n <= 99). $0 is already handled by now.
if (!this.between(nextChCode, ONE_CHAR_CODE, NINE_CHAR_CODE)) {
break;
}
if (i === replaceString.length - 1) {
this._hasParameters = true;
break;
}
let charCode= replaceString.charCodeAt(++i);
if (!this.between(charCode, ZERO_CHAR_CODE, NINE_CHAR_CODE)) {
this._hasParameters = true;
--i;
break;
}
if (i === replaceString.length - 1) {
this._hasParameters = true;
break;
}
charCode= replaceString.charCodeAt(++i);
if (!this.between(charCode, ZERO_CHAR_CODE, NINE_CHAR_CODE)) {
this._hasParameters = true;
--i;
break;
}
break;
}
if (replaceWithCharacter) {
result += replaceString.substring(substrFrom, i - 1) + replaceWithCharacter;
substrFrom = i + 1;
}
}
}
if (substrFrom === 0) {
// no replacement occured
return;
}
this._replacePattern= result + replaceString.substring(substrFrom);
}
private between(value: number, from: number, to: number): boolean {
return from <= value && value <= to;
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { ReplacePattern } from 'vs/platform/search/common/replace';
suite('Replace Pattern test', () => {
test('parse replace string', () => {
let testParse = (input:string, expected:string, expectedHasParameters:boolean) => {
let actual = new ReplacePattern(input, {pattern: 'somepattern', isRegExp: true});
assert.equal(expected, actual.pattern);
assert.equal(expectedHasParameters, actual.hasParameters);
actual= new ReplacePattern('hello' + input + 'hi', {pattern: 'sonepattern', isRegExp: true});
assert.equal('hello' + expected + 'hi', actual.pattern);
assert.equal(expectedHasParameters, actual.hasParameters);
};
// no backslash => no treatment
testParse('hello', 'hello', false);
// \t => TAB
testParse('\\thello', '\thello', false);
// \n => LF
testParse('\\nhello', '\nhello', false);
// \\t => \t
testParse('\\\\thello', '\\thello', false);
// \\\t => \TAB
testParse('\\\\\\thello', '\\\thello', false);
// \\\\t => \\t
testParse('\\\\\\\\thello', '\\\\thello', false);
// \ at the end => no treatment
testParse('hello\\', 'hello\\', false);
// \ with unknown char => no treatment
testParse('hello\\x', 'hello\\x', false);
// \ with back reference => no treatment
testParse('hello\\0', 'hello\\0', false);
// $1 => no treatment
testParse('hello$1', 'hello$1', true);
// $2 => no treatment
testParse('hello$2', 'hello$2', true);
// $12 => no treatment
testParse('hello$12', 'hello$12', true);
// $99 => no treatment
testParse('hello$99', 'hello$99', true);
// $99a => no treatment
testParse('hello$99a', 'hello$99a', true);
// $100 => no treatment
testParse('hello$100', 'hello$100', false);
// $100a => no treatment
testParse('hello$100a', 'hello$100a', false);
// $10a0 => no treatment
testParse('hello$10a0', 'hello$10a0', true);
// $$ => no treatment
testParse('hello$$', 'hello$$', false);
// $$0 => no treatment
testParse('hello$$0', 'hello$$0', false);
// $0 => $&
testParse('hello$0', 'hello$&', true);
testParse('hello$02', 'hello$&2', true);
});
test('get replace string for a matched string', () => {
let testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true});
let actual= testObject.getReplaceString('bla');
assert.equal('hello', actual);
testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: false});
actual= testObject.getReplaceString('bla');
assert.equal('hello', actual);
testObject= new ReplacePattern('hello', {pattern: '(bla)', isRegExp: true});
actual= testObject.getReplaceString('bla');
assert.equal('hello', actual);
testObject= new ReplacePattern('hello$0', {pattern: '(bla)', isRegExp: true});
actual= testObject.getReplaceString('bla');
assert.equal('hellobla', actual);
testObject= new ReplacePattern('import * as $1 from \'$2\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true});
actual= testObject.getReplaceString('let fs = require(\'fs\')');
assert.equal('import * as fs from \'fs\';', actual);
actual= testObject.getReplaceString('let something = require(\'fs\')');
assert.equal('import * as something from \'fs\';', actual);
actual= testObject.getReplaceString('let require(\'fs\')');
assert.equal('let require(\'fs\')', actual);
testObject= new ReplacePattern('import * as $1 from \'$1\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true});
actual= testObject.getReplaceString('let something = require(\'fs\')');
assert.equal('import * as something from \'something\';', actual);
testObject= new ReplacePattern('import * as $2 from \'$1\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true});
actual= testObject.getReplaceString('let something = require(\'fs\')');
assert.equal('import * as fs from \'something\';', actual);
testObject= new ReplacePattern('import * as $0 from \'$0\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true});
actual= testObject.getReplaceString('let something = require(\'fs\');');
assert.equal('import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';;', actual);
testObject= new ReplacePattern('import * as $1 from \'$2\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: false});
actual= testObject.getReplaceString('let fs = require(\'fs\');');
assert.equal('import * as $1 from \'$2\';', actual);
testObject= new ReplacePattern('cat$1', {pattern: 'for(.*)', isRegExp: true});
actual= testObject.getReplaceString('for ()');
assert.equal('cat ()', actual);
// Not maching cases
testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true});
actual= testObject.getReplaceString('foo');
assert.equal('hello', actual);
testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: false});
actual= testObject.getReplaceString('foo');
assert.equal('hello', actual);
});
});
\ No newline at end of file
...@@ -23,27 +23,25 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; ...@@ -23,27 +23,25 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
class EditorInputCache { class EditorInputCache {
private cache: Map.SimpleMap<URI, TPromise<DiffEditorInput>>; private cache: Map.SimpleMap<URI, TPromise<DiffEditorInput>>;
private replaceTextcache: Map.SimpleMap<URI, string>;
constructor(private replaceService: ReplaceService, private editorService: IWorkbenchEditorService, constructor(private replaceService: ReplaceService, private editorService: IWorkbenchEditorService,
private modelService: IModelService) { private modelService: IModelService) {
this.cache= new Map.SimpleMap<URI, TPromise<DiffEditorInput>>(); this.cache= new Map.SimpleMap<URI, TPromise<DiffEditorInput>>();
this.replaceTextcache= new Map.SimpleMap<URI, string>();
} }
public getInput(fileMatch: FileMatch, text: string): TPromise<DiffEditorInput> { public getInput(fileMatch: FileMatch): TPromise<DiffEditorInput> {
let editorInputPromise= this.cache.get(fileMatch.resource()); let editorInputPromise= this.cache.get(fileMatch.resource());
if (!editorInputPromise) { if (!editorInputPromise) {
editorInputPromise= this.createInput(fileMatch); editorInputPromise= this.createInput(fileMatch);
this.cache.set(fileMatch.resource(), editorInputPromise); this.cache.set(fileMatch.resource(), editorInputPromise);
this.refreshInput(fileMatch, text, true); this.refreshInput(fileMatch, true);
fileMatch.onDispose(() => this.disposeInput(fileMatch)); fileMatch.onDispose(() => this.disposeInput(fileMatch));
fileMatch.onChange((modelChange) => this.refreshInput(fileMatch, this.replaceTextcache.get(fileMatch.resource()), modelChange)); fileMatch.onChange((modelChange) => this.refreshInput(fileMatch, modelChange));
} }
return editorInputPromise; return editorInputPromise;
} }
public refreshInput(fileMatch: FileMatch, text: string, reloadFromSource: boolean= false): void { public refreshInput(fileMatch: FileMatch, reloadFromSource: boolean= false): void {
let editorInputPromise= this.cache.get(fileMatch.resource()); let editorInputPromise= this.cache.get(fileMatch.resource());
if (editorInputPromise) { if (editorInputPromise) {
editorInputPromise.done(() => { editorInputPromise.done(() => {
...@@ -51,14 +49,13 @@ class EditorInputCache { ...@@ -51,14 +49,13 @@ class EditorInputCache {
this.editorService.resolveEditorModel({resource: fileMatch.resource()}).then((value: ITextEditorModel) => { this.editorService.resolveEditorModel({resource: fileMatch.resource()}).then((value: ITextEditorModel) => {
let replaceResource= this.getReplaceResource(fileMatch.resource()); let replaceResource= this.getReplaceResource(fileMatch.resource());
this.modelService.getModel(replaceResource).setValue((<IModel> value.textEditorModel).getValue()); this.modelService.getModel(replaceResource).setValue((<IModel> value.textEditorModel).getValue());
this.replaceService.replace(fileMatch, text, null, replaceResource); this.replaceService.replace(fileMatch, null, replaceResource);
}); });
} else { } else {
let replaceResource= this.getReplaceResource(fileMatch.resource()); let replaceResource= this.getReplaceResource(fileMatch.resource());
this.modelService.getModel(replaceResource).undo(); this.modelService.getModel(replaceResource).undo();
this.replaceService.replace(fileMatch, text, null, replaceResource); this.replaceService.replace(fileMatch, null, replaceResource);
} }
this.replaceTextcache.set(fileMatch.resource(), text);
}); });
} }
} }
...@@ -73,7 +70,6 @@ class EditorInputCache { ...@@ -73,7 +70,6 @@ class EditorInputCache {
editorInputPromise.done((diffInput) => { editorInputPromise.done((diffInput) => {
this.disposeReplaceInput(this.getReplaceResource(resourceUri), diffInput); this.disposeReplaceInput(this.getReplaceResource(resourceUri), diffInput);
this.cache.delete(resourceUri); this.cache.delete(resourceUri);
this.replaceTextcache.delete(resourceUri);
}); });
} }
} }
...@@ -127,16 +123,17 @@ export class ReplaceService implements IReplaceService { ...@@ -127,16 +123,17 @@ export class ReplaceService implements IReplaceService {
this.cache= new EditorInputCache(this, editorService, modelService); this.cache= new EditorInputCache(this, editorService, modelService);
} }
public replace(match: Match, text: string): TPromise<any> public replace(match: Match): TPromise<any>
public replace(files: FileMatch[], text: string, progress?: IProgressRunner): TPromise<any> public replace(files: FileMatch[], progress?: IProgressRunner): TPromise<any>
public replace(match: FileMatchOrMatch, text: string, progress?: IProgressRunner, resource?: URI): TPromise<any> public replace(match: FileMatchOrMatch, progress?: IProgressRunner, resource?: URI): TPromise<any>
public replace(arg: any, text: string, progress: IProgressRunner= null, resource: URI= null): TPromise<any> { public replace(arg: any, progress: IProgressRunner= null, resource: URI= null): TPromise<any> {
let bulkEdit: BulkEdit = createBulkEdit(this.eventService, this.editorService, null); let bulkEdit: BulkEdit = createBulkEdit(this.eventService, this.editorService, null);
bulkEdit.progress(progress); bulkEdit.progress(progress);
if (arg instanceof Match) { if (arg instanceof Match) {
bulkEdit.add([this.createEdit(arg, text, resource)]); let match= <Match>arg;
bulkEdit.add([this.createEdit(match, match.replaceString, resource)]);
} }
if (arg instanceof FileMatch) { if (arg instanceof FileMatch) {
...@@ -148,7 +145,7 @@ export class ReplaceService implements IReplaceService { ...@@ -148,7 +145,7 @@ export class ReplaceService implements IReplaceService {
let fileMatch = <FileMatch>element; let fileMatch = <FileMatch>element;
if (fileMatch.count() > 0) { if (fileMatch.count() > 0) {
fileMatch.matches().forEach(match => { fileMatch.matches().forEach(match => {
bulkEdit.add([this.createEdit(match, text, resource)]); bulkEdit.add([this.createEdit(match, match.replaceString, resource)]);
}); });
} }
}); });
...@@ -157,12 +154,12 @@ export class ReplaceService implements IReplaceService { ...@@ -157,12 +154,12 @@ export class ReplaceService implements IReplaceService {
return bulkEdit.finish(); return bulkEdit.finish();
} }
public getInput(element: FileMatch, text: string): TPromise<EditorInput> { public getInput(element: FileMatch): TPromise<EditorInput> {
return this.cache.getInput(element, text); return this.cache.getInput(element);
} }
public refreshInput(element: FileMatch, text: string, reload: boolean= false): void { public refreshInput(element: FileMatch, reload: boolean= false): void {
this.cache.refreshInput(element, text, reload); this.cache.refreshInput(element, reload);
} }
public disposeAllInputs(): void { public disposeAllInputs(): void {
......
...@@ -185,7 +185,7 @@ export class ReplaceAllAction extends Action { ...@@ -185,7 +185,7 @@ export class ReplaceAllAction extends Action {
public run(): TPromise<any> { public run(): TPromise<any> {
this.telemetryService.publicLog('replaceAll.action.selected'); this.telemetryService.publicLog('replaceAll.action.selected');
return this.fileMatch.parent().replace(this.fileMatch, this.fileMatch.parent().searchModel.replaceText).then(() => { return this.fileMatch.parent().replace(this.fileMatch).then(() => {
this.viewlet.open(this.fileMatch); this.viewlet.open(this.fileMatch);
}); });
} }
...@@ -206,7 +206,7 @@ export class ReplaceAction extends Action { ...@@ -206,7 +206,7 @@ export class ReplaceAction extends Action {
public run(): TPromise<any> { public run(): TPromise<any> {
this.telemetryService.publicLog('replace.action.selected'); this.telemetryService.publicLog('replace.action.selected');
return this.element.parent().replace(this.element, this.element.parent().parent().searchModel.replaceText).then(() => { return this.element.parent().replace(this.element).then(() => {
this.viewlet.open(this.element); this.viewlet.open(this.element);
}); });
} }
......
...@@ -161,20 +161,19 @@ export class SearchRenderer extends ActionsRenderer { ...@@ -161,20 +161,19 @@ export class SearchRenderer extends ActionsRenderer {
// Match // Match
else if (element instanceof Match) { else if (element instanceof Match) {
dom.addClass(domElement, 'linematch'); dom.addClass(domElement, 'linematch');
let match= <Match>element;
let elements: string[] = []; let elements: string[] = [];
let preview = element.preview(); let preview = match.preview();
elements.push('<span>'); elements.push('<span>');
elements.push(strings.escape(preview.before)); elements.push(strings.escape(preview.before));
let input= <SearchResult>tree.getInput(); let showReplaceText= (<SearchResult>tree.getInput()).searchModel.hasReplaceString();
let showReplaceText= input.searchModel.hasReplaceText();
elements.push('</span><span class="' + (showReplaceText ? 'replace ' : '') + 'findInFileMatch">'); elements.push('</span><span class="' + (showReplaceText ? 'replace ' : '') + 'findInFileMatch">');
elements.push(strings.escape(preview.inside)); elements.push(strings.escape(preview.inside));
if (showReplaceText) { if (showReplaceText) {
elements.push('</span><span class="replaceMatch">'); elements.push('</span><span class="replaceMatch">');
elements.push(strings.escape(input.searchModel.replaceText)); elements.push(strings.escape(match.replaceString));
} }
elements.push('</span><span>'); elements.push('</span><span>');
elements.push(strings.escape(preview.after)); elements.push(strings.escape(preview.after));
...@@ -182,7 +181,7 @@ export class SearchRenderer extends ActionsRenderer { ...@@ -182,7 +181,7 @@ export class SearchRenderer extends ActionsRenderer {
$('a.plain') $('a.plain')
.innerHtml(elements.join(strings.empty)) .innerHtml(elements.join(strings.empty))
.title((preview.before + (input.searchModel.isReplaceActive() ? input.searchModel.replaceText : preview.inside) + preview.after).trim().substr(0, 999)) .title((preview.before + (showReplaceText ? match.replaceString : preview.inside) + preview.after).trim().substr(0, 999))
.appendTo(domElement); .appendTo(domElement);
} }
...@@ -203,12 +202,13 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider { ...@@ -203,12 +202,13 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider {
} }
if (element instanceof Match) { if (element instanceof Match) {
let match= <Match> element;
let input= <SearchResult>tree.getInput(); let input= <SearchResult>tree.getInput();
if (input.searchModel.isReplaceActive()) { if (input.searchModel.isReplaceActive()) {
let preview = element.preview(); let preview = match.preview();
return nls.localize('replacePreviewResultAria', "Replace preview result, {0}", preview.before + input.searchModel.replaceText + preview.after); return nls.localize('replacePreviewResultAria', "Replace preview result, {0}", preview.before + match.replaceString + preview.after);
} }
return nls.localize('searchResultAria', "{0}, Search result", element.text()); return nls.localize('searchResultAria', "{0}, Search result", match.text());
} }
} }
} }
......
...@@ -290,11 +290,11 @@ export class SearchViewlet extends Viewlet { ...@@ -290,11 +290,11 @@ export class SearchViewlet extends Viewlet {
this.toUnbind.push(this.searchWidget.onReplaceToggled(() => this.onReplaceToggled())); this.toUnbind.push(this.searchWidget.onReplaceToggled(() => this.onReplaceToggled()));
this.toUnbind.push(this.searchWidget.onReplaceStateChange((state) => { this.toUnbind.push(this.searchWidget.onReplaceStateChange((state) => {
this.viewModel.replaceText= this.searchWidget.getReplaceValue(); this.viewModel.replaceString= this.searchWidget.getReplaceValue();
this.tree.refresh(); this.tree.refresh();
})); }));
this.toUnbind.push(this.searchWidget.onReplaceValueChanged((value) => { this.toUnbind.push(this.searchWidget.onReplaceValueChanged((value) => {
this.viewModel.replaceText= this.searchWidget.getReplaceValue(); this.viewModel.replaceString= this.searchWidget.getReplaceValue();
this.refreshInputs(); this.refreshInputs();
this.tree.refresh(); this.tree.refresh();
})); }));
...@@ -344,7 +344,7 @@ export class SearchViewlet extends Viewlet { ...@@ -344,7 +344,7 @@ export class SearchViewlet extends Viewlet {
private refreshInputs(): void { private refreshInputs(): void {
this.viewModel.searchResult.matches().forEach((fileMatch) => { this.viewModel.searchResult.matches().forEach((fileMatch) => {
this.replaceService.refreshInput(fileMatch, this.viewModel.replaceText); this.replaceService.refreshInput(fileMatch);
}); });
} }
...@@ -370,7 +370,7 @@ export class SearchViewlet extends Viewlet { ...@@ -370,7 +370,7 @@ export class SearchViewlet extends Viewlet {
if (this.messageService.confirm(confirmation)) { if (this.messageService.confirm(confirmation)) {
this.searchWidget.setReplaceAllActionState(false); this.searchWidget.setReplaceAllActionState(false);
this.viewModel.searchResult.replaceAll(replaceValue, progressRunner).then(() => { this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
progressRunner.done(); progressRunner.done();
this.showMessage(afterReplaceAllMessage); this.showMessage(afterReplaceAllMessage);
}, (error) => { }, (error) => {
...@@ -731,7 +731,7 @@ export class SearchViewlet extends Viewlet { ...@@ -731,7 +731,7 @@ export class SearchViewlet extends Viewlet {
} }
this.onSearchResultsChanged().then(() => autoExpand(true)); this.onSearchResultsChanged().then(() => autoExpand(true));
this.viewModel.replaceText= this.searchWidget.getReplaceValue(); this.viewModel.replaceString= this.searchWidget.getReplaceValue();
let hasResults = !this.viewModel.searchResult.isEmpty(); let hasResults = !this.viewModel.searchResult.isEmpty();
this.loading = false; this.loading = false;
...@@ -902,7 +902,7 @@ export class SearchViewlet extends Viewlet { ...@@ -902,7 +902,7 @@ export class SearchViewlet extends Viewlet {
this.telemetryService.publicLog('searchResultChosen'); this.telemetryService.publicLog('searchResultChosen');
return this.viewModel.hasReplaceText() ? this.openReplacePreviewEditor(lineMatch, preserveFocus, sideBySide, pinned) : this.open(lineMatch, preserveFocus, sideBySide, pinned); return this.viewModel.hasReplaceString() ? this.openReplacePreviewEditor(lineMatch, preserveFocus, sideBySide, pinned) : this.open(lineMatch, preserveFocus, sideBySide, pinned);
} }
public open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> { public open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
...@@ -920,7 +920,7 @@ export class SearchViewlet extends Viewlet { ...@@ -920,7 +920,7 @@ export class SearchViewlet extends Viewlet {
private openReplacePreviewEditor(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> { private openReplacePreviewEditor(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
this.telemetryService.publicLog('replace.open.previewEditor'); this.telemetryService.publicLog('replace.open.previewEditor');
return this.replaceService.getInput(element instanceof Match ? element.parent() : element, this.viewModel.replaceText).then((editorInput) => { return this.replaceService.getInput(element instanceof Match ? element.parent() : element).then((editorInput) => {
this.editorService.openEditor(editorInput, {preserveFocus: preserveFocus, pinned}).then((editor) => { this.editorService.openEditor(editorInput, {preserveFocus: preserveFocus, pinned}).then((editor) => {
let editorControl= (<IDiffEditor>editor.getControl()); let editorControl= (<IDiffEditor>editor.getControl());
if (element instanceof Match) { if (element instanceof Match) {
...@@ -941,12 +941,12 @@ export class SearchViewlet extends Viewlet { ...@@ -941,12 +941,12 @@ export class SearchViewlet extends Viewlet {
if (match) { if (match) {
let range= match.range(); let range= match.range();
if (this.viewModel.isReplaceActive()) { if (this.viewModel.isReplaceActive()) {
let replaceText= this.viewModel.replaceText; let replaceString= match.replaceString;
return { return {
startLineNumber: range.startLineNumber, startLineNumber: range.startLineNumber,
startColumn: range.startColumn + replaceText.length, startColumn: range.startColumn + replaceString.length,
endLineNumber: range.startLineNumber, endLineNumber: range.startLineNumber,
endColumn: range.startColumn + replaceText.length endColumn: range.startColumn + replaceString.length
}; };
} }
return range; return range;
......
...@@ -16,26 +16,26 @@ export interface IReplaceService { ...@@ -16,26 +16,26 @@ export interface IReplaceService {
_serviceBrand: any; _serviceBrand: any;
/** /**
* Replace the match with the given text. * Replaces the given match in the file that match belongs to
*/ */
replace(match: Match, text: string): TPromise<any>; replace(match: Match): TPromise<any>;
/** /**
* Replace all the matches in the given file matches with provided text. * Replace all the matches from the given file matches in the files
* You can also pass the progress runner to update the progress of replacing. * You can also pass the progress runner to update the progress of replacing.
*/ */
replace(files: FileMatch[], text: string, progress?: IProgressRunner): TPromise<any>; replace(files: FileMatch[], progress?: IProgressRunner): TPromise<any>;
/** /**
* Gets the input for the file match with given text * Gets the input for the file match
*/ */
getInput(element: FileMatch, text: string): TPromise<EditorInput>; getInput(element: FileMatch): TPromise<EditorInput>;
/** /**
* Refresh the input for the fiel match with given text. If reload, content of repalced editor is reloaded completely * Refresh the input for the file match. If reload, content of repalced editor is reloaded completely
* Otherwise undo the last changes and refreshes with new text. * Otherwise undo the last changes and refreshes with new text.
*/ */
refreshInput(element: FileMatch, text: string, reload?: boolean): void; refreshInput(element: FileMatch, reload?: boolean): void;
/** /**
* Disposes all Inputs * Disposes all Inputs
......
...@@ -16,6 +16,7 @@ import { ArraySet } from 'vs/base/common/set'; ...@@ -16,6 +16,7 @@ import { ArraySet } from 'vs/base/common/set';
import Event, { Emitter } from 'vs/base/common/event'; import Event, { Emitter } from 'vs/base/common/event';
import * as Search from 'vs/platform/search/common/search'; import * as Search from 'vs/platform/search/common/search';
import { ISearchProgressItem, ISearchComplete, ISearchQuery } from 'vs/platform/search/common/search'; import { ISearchProgressItem, ISearchComplete, ISearchQuery } from 'vs/platform/search/common/search';
import { ReplacePattern } from 'vs/platform/search/common/replace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { IModel, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, IModelDecorationOptions } from 'vs/editor/common/editorCommon'; import { IModel, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
...@@ -66,6 +67,10 @@ export class Match { ...@@ -66,6 +67,10 @@ export class Match {
after, after,
}; };
} }
public get replaceString(): string {
return this.parent().parent().searchModel.replacePattern.getReplaceString(this.preview().inside);
}
} }
export class FileMatch extends Disposable { export class FileMatch extends Disposable {
...@@ -208,8 +213,8 @@ export class FileMatch extends Disposable { ...@@ -208,8 +213,8 @@ export class FileMatch extends Disposable {
this._onChange.fire(false); this._onChange.fire(false);
} }
public replace(match: Match, replaceText: string): TPromise<any> { public replace(match: Match): TPromise<any> {
return this.replaceService.replace(match, replaceText).then(() => { return this.replaceService.replace(match).then(() => {
this._matches.delete(match.id()); this._matches.delete(match.id());
this._onChange.fire(false); this._onChange.fire(false);
}); });
...@@ -299,16 +304,16 @@ export class SearchResult extends Disposable { ...@@ -299,16 +304,16 @@ export class SearchResult extends Disposable {
this.doRemove(match); this.doRemove(match);
} }
public replace(match: FileMatch, replaceText: string): TPromise<any> { public replace(match: FileMatch): TPromise<any> {
return this.replaceService.replace([match], replaceText).then(() => { return this.replaceService.replace([match]).then(() => {
this.doRemove(match, false, true); this.doRemove(match, false, true);
}); });
} }
public replaceAll(replaceText: string, progressRunner: IProgressRunner): TPromise<any> { public replaceAll(progressRunner: IProgressRunner): TPromise<any> {
this._replacingAll= true; this._replacingAll= true;
let replaceAllTimer = this.telemetryService.timedPublicLog('replaceAll.started'); let replaceAllTimer = this.telemetryService.timedPublicLog('replaceAll.started');
return this.replaceService.replace(this.matches(), replaceText, progressRunner).then(() => { return this.replaceService.replace(this.matches(), progressRunner).then(() => {
replaceAllTimer.stop(); replaceAllTimer.stop();
this._replacingAll= false; this._replacingAll= false;
this.clear(); this.clear();
...@@ -402,7 +407,8 @@ export class SearchModel extends Disposable { ...@@ -402,7 +407,8 @@ export class SearchModel extends Disposable {
private _searchResult: SearchResult; private _searchResult: SearchResult;
private _searchQuery: ISearchQuery= null; private _searchQuery: ISearchQuery= null;
private _replaceText: string= null; private _replaceString: string= null;
private _replacePattern: ReplacePattern= null;
private currentRequest: PPromise<ISearchComplete, ISearchProgressItem>; private currentRequest: PPromise<ISearchComplete, ISearchProgressItem>;
private progressTimer: timer.ITimerEvent; private progressTimer: timer.ITimerEvent;
...@@ -418,7 +424,15 @@ export class SearchModel extends Disposable { ...@@ -418,7 +424,15 @@ export class SearchModel extends Disposable {
* Return true if replace is enabled otherwise false * Return true if replace is enabled otherwise false
*/ */
public isReplaceActive():boolean { public isReplaceActive():boolean {
return this.replaceText !== null && this.replaceText !== void 0; return this._replaceString !== null && this._replaceString !== void 0;
}
/**
* Return true if replace is enabled and replace text is not empty, otherwise false.
* This is necessary in cases handling empty replace text when replace is active.
*/
public hasReplaceString():boolean {
return this.isReplaceActive() && !!this._replaceString;
} }
/** /**
...@@ -426,37 +440,34 @@ export class SearchModel extends Disposable { ...@@ -426,37 +440,34 @@ export class SearchModel extends Disposable {
* Can be null if replace is not enabled. Use replace() before. * Can be null if replace is not enabled. Use replace() before.
* Can be empty. * Can be empty.
*/ */
public get replaceText(): string { public get replacePattern(): ReplacePattern {
return this._replaceText; return this._replacePattern;
} }
public set replaceText(replace: string) { public set replaceString(replaceString: string) {
this._replaceText= replace; this._replaceString= replaceString;
if (this._searchQuery) {
this._replacePattern= new ReplacePattern(replaceString, this._searchQuery.contentPattern);
}
} }
public get searchResult():SearchResult { public get searchResult():SearchResult {
return this._searchResult; return this._searchResult;
} }
/**
* Return true if replace is enabled and replace text is not empty, otherwise false.
* This is necessary in cases handling empty replace text when replace is active.
*/
public hasReplaceText():boolean {
return this.isReplaceActive() && !!this.replaceText;
}
public search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> { public search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
this.cancelSearch(); this.cancelSearch();
this.searchResult.clear(); this.searchResult.clear();
this._searchQuery= query; this._searchQuery= query;
this._searchResult.query= this._searchQuery.contentPattern; this._searchResult.query= this._searchQuery.contentPattern;
this._replacePattern= new ReplacePattern(this._replaceString, this._searchQuery.contentPattern);
this.progressTimer = this.telemetryService.timedPublicLog('searchResultsFirstRender'); this.progressTimer = this.telemetryService.timedPublicLog('searchResultsFirstRender');
this.doneTimer = this.telemetryService.timedPublicLog('searchResultsFinished'); this.doneTimer = this.telemetryService.timedPublicLog('searchResultsFinished');
this.timerEvent = timer.start(timer.Topic.WORKBENCH, 'Search'); this.timerEvent = timer.start(timer.Topic.WORKBENCH, 'Search');
this.currentRequest = this.searchService.search(this._searchQuery);
this.currentRequest = this.searchService.search(this._searchQuery);
this.currentRequest.then(value => this.onSearchCompleted(value), this.currentRequest.then(value => this.onSearchCompleted(value),
e => this.onSearchError(e), e => this.onSearchError(e),
p => this.onSearchProgress(p)); p => this.onSearchProgress(p));
......
...@@ -230,28 +230,28 @@ suite('SearchModel', () => { ...@@ -230,28 +230,28 @@ suite('SearchModel', () => {
test('Search Model: isReplaceActive return false if replace text is set to null', function () { test('Search Model: isReplaceActive return false if replace text is set to null', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= null; testObject.replaceString= null;
assert.ok(!testObject.isReplaceActive()); assert.ok(!testObject.isReplaceActive());
}); });
test('Search Model: isReplaceActive return false if replace text is set to undefined', function () { test('Search Model: isReplaceActive return false if replace text is set to undefined', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= void 0; testObject.replaceString= void 0;
assert.ok(!testObject.isReplaceActive()); assert.ok(!testObject.isReplaceActive());
}); });
test('Search Model: isReplaceActive return true if replace text is set to empty string', function () { test('Search Model: isReplaceActive return true if replace text is set to empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= ''; testObject.replaceString= '';
assert.ok(testObject.isReplaceActive()); assert.ok(testObject.isReplaceActive());
}); });
test('Search Model: isReplaceActive return true if replace text is set to non empty string', function () { test('Search Model: isReplaceActive return true if replace text is set to non empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= 'some value'; testObject.replaceString= 'some value';
assert.ok(testObject.isReplaceActive()); assert.ok(testObject.isReplaceActive());
}); });
...@@ -259,35 +259,35 @@ suite('SearchModel', () => { ...@@ -259,35 +259,35 @@ suite('SearchModel', () => {
test('Search Model: hasReplaceText return false if no replace text is set', function () { test('Search Model: hasReplaceText return false if no replace text is set', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
assert.ok(!testObject.hasReplaceText()); assert.ok(!testObject.hasReplaceString());
}); });
test('Search Model: hasReplaceText return false if replace text is set to null', function () { test('Search Model: hasReplaceText return false if replace text is set to null', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= null; testObject.replaceString= null;
assert.ok(!testObject.hasReplaceText()); assert.ok(!testObject.hasReplaceString());
}); });
test('Search Model: hasReplaceText return false if replace text is set to undefined', function () { test('Search Model: hasReplaceText return false if replace text is set to undefined', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= void 0; testObject.replaceString= void 0;
assert.ok(!testObject.hasReplaceText()); assert.ok(!testObject.hasReplaceString());
}); });
test('Search Model: hasReplaceText return false if replace text is set to empty string', function () { test('Search Model: hasReplaceText return false if replace text is set to empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= ''; testObject.replaceString= '';
assert.ok(!testObject.hasReplaceText()); assert.ok(!testObject.hasReplaceString());
}); });
test('Search Model: hasReplaceText return true if replace text is set to non empty string', function () { test('Search Model: hasReplaceText return true if replace text is set to non empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel); let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= 'some value'; testObject.replaceString= 'some value';
assert.ok(testObject.hasReplaceText()); assert.ok(testObject.hasReplaceString());
}); });
function aRawMatch(resource: string, ...lineMatches: ILineMatch[]): IFileMatch { function aRawMatch(resource: string, ...lineMatches: ILineMatch[]): IFileMatch {
......
...@@ -180,7 +180,7 @@ suite('SearchResult', () => { ...@@ -180,7 +180,7 @@ suite('SearchResult', () => {
let testObject = aSearchResult(); let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]); testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.replace(testObject.matches()[0], ''); testObject.replace(testObject.matches()[0]);
assert.ok(testObject.isEmpty()); assert.ok(testObject.isEmpty());
}); });
...@@ -193,7 +193,7 @@ suite('SearchResult', () => { ...@@ -193,7 +193,7 @@ suite('SearchResult', () => {
testObject.onChange(target); testObject.onChange(target);
let objectRoRemove= testObject.matches()[0]; let objectRoRemove= testObject.matches()[0];
testObject.replace(objectRoRemove, ''); testObject.replace(objectRoRemove);
assert.ok(target.calledOnce); assert.ok(target.calledOnce);
assert.deepEqual([{elements: [objectRoRemove], removed: true}], target.args[0]); assert.deepEqual([{elements: [objectRoRemove], removed: true}], target.args[0]);
...@@ -204,7 +204,7 @@ suite('SearchResult', () => { ...@@ -204,7 +204,7 @@ suite('SearchResult', () => {
let testObject = aSearchResult(); 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', aLineMatch('preview 1')), aRawMatch('file://c:/2', aLineMatch('preview 2'))]);
testObject.replaceAll('', null); testObject.replaceAll(null);
assert.ok(testObject.isEmpty()); assert.ok(testObject.isEmpty());
}); });
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册