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

Implement #9010

上级 d926a2f0
......@@ -7,6 +7,7 @@
import {RunOnceScheduler} from 'vs/base/common/async';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
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 {Position} from 'vs/editor/common/core/position';
import {Range} from 'vs/editor/common/core/range';
......@@ -290,13 +291,8 @@ export class FindModelBoundToEditorModel {
}
private getReplaceString(matchedString:string): string {
if (!this._state.isRegex) {
return this._state.replaceString;
}
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);
let replacePattern= new ReplacePattern(this._state.replaceString, {pattern: this._state.searchString, isRegExp: this._state.isRegex, isCaseSensitive: this._state.matchCase, isWordMatch: this._state.wholeWord});
return replacePattern.getReplaceString(matchedString);
}
private _rangeIsMatch(range:Range): boolean {
......@@ -378,95 +374,4 @@ export class FindModelBoundToEditorModel {
this._ignoreModelContentChanged = false;
}
}
}
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);
}
}
\ No newline at end of file
......@@ -10,66 +10,12 @@ import {Position} from 'vs/editor/common/core/position';
import {Selection} from 'vs/editor/common/core/selection';
import {Range} from 'vs/editor/common/core/range';
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 {withMockCodeEditor} from 'vs/editor/test/common/mocks/mockCodeEditor';
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 {
test(testName, () => {
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';
class EditorInputCache {
private cache: Map.SimpleMap<URI, TPromise<DiffEditorInput>>;
private replaceTextcache: Map.SimpleMap<URI, string>;
constructor(private replaceService: ReplaceService, private editorService: IWorkbenchEditorService,
private modelService: IModelService) {
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());
if (!editorInputPromise) {
editorInputPromise= this.createInput(fileMatch);
this.cache.set(fileMatch.resource(), editorInputPromise);
this.refreshInput(fileMatch, text, true);
this.refreshInput(fileMatch, true);
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;
}
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());
if (editorInputPromise) {
editorInputPromise.done(() => {
......@@ -51,14 +49,13 @@ class EditorInputCache {
this.editorService.resolveEditorModel({resource: fileMatch.resource()}).then((value: ITextEditorModel) => {
let replaceResource= this.getReplaceResource(fileMatch.resource());
this.modelService.getModel(replaceResource).setValue((<IModel> value.textEditorModel).getValue());
this.replaceService.replace(fileMatch, text, null, replaceResource);
this.replaceService.replace(fileMatch, null, replaceResource);
});
} else {
let replaceResource= this.getReplaceResource(fileMatch.resource());
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 {
editorInputPromise.done((diffInput) => {
this.disposeReplaceInput(this.getReplaceResource(resourceUri), diffInput);
this.cache.delete(resourceUri);
this.replaceTextcache.delete(resourceUri);
});
}
}
......@@ -127,16 +123,17 @@ export class ReplaceService implements IReplaceService {
this.cache= new EditorInputCache(this, editorService, modelService);
}
public replace(match: Match, text: string): TPromise<any>
public replace(files: FileMatch[], text: string, progress?: IProgressRunner): TPromise<any>
public replace(match: FileMatchOrMatch, text: string, progress?: IProgressRunner, resource?: URI): TPromise<any>
public replace(arg: any, text: string, progress: IProgressRunner= null, resource: URI= null): TPromise<any> {
public replace(match: Match): TPromise<any>
public replace(files: FileMatch[], progress?: IProgressRunner): TPromise<any>
public replace(match: FileMatchOrMatch, progress?: IProgressRunner, resource?: URI): TPromise<any>
public replace(arg: any, progress: IProgressRunner= null, resource: URI= null): TPromise<any> {
let bulkEdit: BulkEdit = createBulkEdit(this.eventService, this.editorService, null);
bulkEdit.progress(progress);
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) {
......@@ -148,7 +145,7 @@ export class ReplaceService implements IReplaceService {
let fileMatch = <FileMatch>element;
if (fileMatch.count() > 0) {
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 {
return bulkEdit.finish();
}
public getInput(element: FileMatch, text: string): TPromise<EditorInput> {
return this.cache.getInput(element, text);
public getInput(element: FileMatch): TPromise<EditorInput> {
return this.cache.getInput(element);
}
public refreshInput(element: FileMatch, text: string, reload: boolean= false): void {
this.cache.refreshInput(element, text, reload);
public refreshInput(element: FileMatch, reload: boolean= false): void {
this.cache.refreshInput(element, reload);
}
public disposeAllInputs(): void {
......
......@@ -185,7 +185,7 @@ export class ReplaceAllAction extends Action {
public run(): TPromise<any> {
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);
});
}
......@@ -206,7 +206,7 @@ export class ReplaceAction extends Action {
public run(): TPromise<any> {
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);
});
}
......
......@@ -161,20 +161,19 @@ export class SearchRenderer extends ActionsRenderer {
// Match
else if (element instanceof Match) {
dom.addClass(domElement, 'linematch');
let match= <Match>element;
let elements: string[] = [];
let preview = element.preview();
let preview = match.preview();
elements.push('<span>');
elements.push(strings.escape(preview.before));
let input= <SearchResult>tree.getInput();
let showReplaceText= input.searchModel.hasReplaceText();
let showReplaceText= (<SearchResult>tree.getInput()).searchModel.hasReplaceString();
elements.push('</span><span class="' + (showReplaceText ? 'replace ' : '') + 'findInFileMatch">');
elements.push(strings.escape(preview.inside));
if (showReplaceText) {
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(strings.escape(preview.after));
......@@ -182,7 +181,7 @@ export class SearchRenderer extends ActionsRenderer {
$('a.plain')
.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);
}
......@@ -203,12 +202,13 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider {
}
if (element instanceof Match) {
let match= <Match> element;
let input= <SearchResult>tree.getInput();
if (input.searchModel.isReplaceActive()) {
let preview = element.preview();
return nls.localize('replacePreviewResultAria', "Replace preview result, {0}", preview.before + input.searchModel.replaceText + preview.after);
let preview = match.preview();
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 {
this.toUnbind.push(this.searchWidget.onReplaceToggled(() => this.onReplaceToggled()));
this.toUnbind.push(this.searchWidget.onReplaceStateChange((state) => {
this.viewModel.replaceText= this.searchWidget.getReplaceValue();
this.viewModel.replaceString= this.searchWidget.getReplaceValue();
this.tree.refresh();
}));
this.toUnbind.push(this.searchWidget.onReplaceValueChanged((value) => {
this.viewModel.replaceText= this.searchWidget.getReplaceValue();
this.viewModel.replaceString= this.searchWidget.getReplaceValue();
this.refreshInputs();
this.tree.refresh();
}));
......@@ -344,7 +344,7 @@ export class SearchViewlet extends Viewlet {
private refreshInputs(): void {
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 {
if (this.messageService.confirm(confirmation)) {
this.searchWidget.setReplaceAllActionState(false);
this.viewModel.searchResult.replaceAll(replaceValue, progressRunner).then(() => {
this.viewModel.searchResult.replaceAll(progressRunner).then(() => {
progressRunner.done();
this.showMessage(afterReplaceAllMessage);
}, (error) => {
......@@ -731,7 +731,7 @@ export class SearchViewlet extends Viewlet {
}
this.onSearchResultsChanged().then(() => autoExpand(true));
this.viewModel.replaceText= this.searchWidget.getReplaceValue();
this.viewModel.replaceString= this.searchWidget.getReplaceValue();
let hasResults = !this.viewModel.searchResult.isEmpty();
this.loading = false;
......@@ -902,7 +902,7 @@ export class SearchViewlet extends Viewlet {
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> {
......@@ -920,7 +920,7 @@ export class SearchViewlet extends Viewlet {
private openReplacePreviewEditor(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
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) => {
let editorControl= (<IDiffEditor>editor.getControl());
if (element instanceof Match) {
......@@ -941,12 +941,12 @@ export class SearchViewlet extends Viewlet {
if (match) {
let range= match.range();
if (this.viewModel.isReplaceActive()) {
let replaceText= this.viewModel.replaceText;
let replaceString= match.replaceString;
return {
startLineNumber: range.startLineNumber,
startColumn: range.startColumn + replaceText.length,
startColumn: range.startColumn + replaceString.length,
endLineNumber: range.startLineNumber,
endColumn: range.startColumn + replaceText.length
endColumn: range.startColumn + replaceString.length
};
}
return range;
......
......@@ -16,26 +16,26 @@ export interface IReplaceService {
_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.
*/
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.
*/
refreshInput(element: FileMatch, text: string, reload?: boolean): void;
refreshInput(element: FileMatch, reload?: boolean): void;
/**
* Disposes all Inputs
......
......@@ -16,6 +16,7 @@ import { ArraySet } from 'vs/base/common/set';
import Event, { Emitter } from 'vs/base/common/event';
import * as Search 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 { Range } from 'vs/editor/common/core/range';
import { IModel, IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
......@@ -66,6 +67,10 @@ export class Match {
after,
};
}
public get replaceString(): string {
return this.parent().parent().searchModel.replacePattern.getReplaceString(this.preview().inside);
}
}
export class FileMatch extends Disposable {
......@@ -208,8 +213,8 @@ export class FileMatch extends Disposable {
this._onChange.fire(false);
}
public replace(match: Match, replaceText: string): TPromise<any> {
return this.replaceService.replace(match, replaceText).then(() => {
public replace(match: Match): TPromise<any> {
return this.replaceService.replace(match).then(() => {
this._matches.delete(match.id());
this._onChange.fire(false);
});
......@@ -299,16 +304,16 @@ export class SearchResult extends Disposable {
this.doRemove(match);
}
public replace(match: FileMatch, replaceText: string): TPromise<any> {
return this.replaceService.replace([match], replaceText).then(() => {
public replace(match: FileMatch): TPromise<any> {
return this.replaceService.replace([match]).then(() => {
this.doRemove(match, false, true);
});
}
public replaceAll(replaceText: string, progressRunner: IProgressRunner): TPromise<any> {
public replaceAll(progressRunner: IProgressRunner): TPromise<any> {
this._replacingAll= true;
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();
this._replacingAll= false;
this.clear();
......@@ -402,7 +407,8 @@ export class SearchModel extends Disposable {
private _searchResult: SearchResult;
private _searchQuery: ISearchQuery= null;
private _replaceText: string= null;
private _replaceString: string= null;
private _replacePattern: ReplacePattern= null;
private currentRequest: PPromise<ISearchComplete, ISearchProgressItem>;
private progressTimer: timer.ITimerEvent;
......@@ -418,7 +424,15 @@ export class SearchModel extends Disposable {
* Return true if replace is enabled otherwise false
*/
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 {
* Can be null if replace is not enabled. Use replace() before.
* Can be empty.
*/
public get replaceText(): string {
return this._replaceText;
public get replacePattern(): ReplacePattern {
return this._replacePattern;
}
public set replaceText(replace: string) {
this._replaceText= replace;
public set replaceString(replaceString: string) {
this._replaceString= replaceString;
if (this._searchQuery) {
this._replacePattern= new ReplacePattern(replaceString, this._searchQuery.contentPattern);
}
}
public get searchResult():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> {
this.cancelSearch();
this.searchResult.clear();
this._searchQuery= query;
this._searchResult.query= this._searchQuery.contentPattern;
this._replacePattern= new ReplacePattern(this._replaceString, this._searchQuery.contentPattern);
this.progressTimer = this.telemetryService.timedPublicLog('searchResultsFirstRender');
this.doneTimer = this.telemetryService.timedPublicLog('searchResultsFinished');
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),
e => this.onSearchError(e),
p => this.onSearchProgress(p));
......
......@@ -230,28 +230,28 @@ suite('SearchModel', () => {
test('Search Model: isReplaceActive return false if replace text is set to null', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= null;
testObject.replaceString= null;
assert.ok(!testObject.isReplaceActive());
});
test('Search Model: isReplaceActive return false if replace text is set to undefined', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= void 0;
testObject.replaceString= void 0;
assert.ok(!testObject.isReplaceActive());
});
test('Search Model: isReplaceActive return true if replace text is set to empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= '';
testObject.replaceString= '';
assert.ok(testObject.isReplaceActive());
});
test('Search Model: isReplaceActive return true if replace text is set to non empty string', function () {
let testObject:SearchModel= instantiationService.createInstance(SearchModel);
testObject.replaceText= 'some value';
testObject.replaceString= 'some value';
assert.ok(testObject.isReplaceActive());
});
......@@ -259,35 +259,35 @@ suite('SearchModel', () => {
test('Search Model: hasReplaceText return false if no replace text is set', function () {
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 () {
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 () {
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 () {
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 () {
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 {
......
......@@ -180,7 +180,7 @@ suite('SearchResult', () => {
let testObject = aSearchResult();
testObject.add([aRawMatch('file://c:/1', aLineMatch('preview 1'))]);
testObject.replace(testObject.matches()[0], '');
testObject.replace(testObject.matches()[0]);
assert.ok(testObject.isEmpty());
});
......@@ -193,7 +193,7 @@ suite('SearchResult', () => {
testObject.onChange(target);
let objectRoRemove= testObject.matches()[0];
testObject.replace(objectRoRemove, '');
testObject.replace(objectRoRemove);
assert.ok(target.calledOnce);
assert.deepEqual([{elements: [objectRoRemove], removed: true}], target.args[0]);
......@@ -204,7 +204,7 @@ suite('SearchResult', () => {
let testObject = aSearchResult();
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());
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册