提交 160d6b85 编写于 作者: S Sandeep Somavarapu

fix #6402

上级 b44c9b26
......@@ -84,6 +84,8 @@ export class SearchViewlet extends Viewlet {
private inputPatternIncludes: PatternInputWidget;
private results: Builder;
private currentSelectedFileMatch: FileMatch;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IEventService private eventService: IEventService,
......@@ -406,7 +408,7 @@ export class SearchViewlet extends Viewlet {
this.toUnbind.push(renderer);
this.toUnbind.push(this.tree.addListener2('selection', (event: any) => {
let element: any;
let element: Match;
let keyboard = event.payload && event.payload.origin === 'keyboard';
if (keyboard) {
element = this.tree.getFocus();
......@@ -424,6 +426,11 @@ export class SearchViewlet extends Viewlet {
let sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
let focusEditor = (keyboard && (<KeyboardEvent>originalEvent).keyCode === KeyCode.Enter) || doubleClick;
if (this.currentSelectedFileMatch) {
this.currentSelectedFileMatch.setSelectedMatch(null);
}
this.currentSelectedFileMatch = element.parent();
this.currentSelectedFileMatch.setSelectedMatch(element);
this.onFocus(element, !focusEditor, sideBySide, doubleClick);
}));
});
......@@ -892,6 +899,7 @@ export class SearchViewlet extends Viewlet {
this.messages.hide();
this.results.show();
this.tree.onVisible();
this.currentSelectedFileMatch = null;
}
private onFocus(lineMatch: Match, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
......
......@@ -34,8 +34,8 @@ export class Match {
constructor(private _parent: FileMatch, text: string, lineNumber: number, offset: number, length: number) {
this._lineText = text;
this._id = this._parent.id() + '>' + lineNumber + '>' + offset;
this._range = new Range(1 + lineNumber, 1 + offset, 1 + lineNumber, 1 + offset + length);
this._id = this._parent.id() + '>' + lineNumber + '>' + offset + this.getMatchString();
}
public id(): string {
......@@ -56,7 +56,7 @@ export class Match {
public preview(): { before: string; inside: string; after: string; } {
let before = this._lineText.substring(0, this._range.startColumn - 1),
inside = this._lineText.substring(this._range.startColumn - 1, this._range.endColumn - 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);
......@@ -69,26 +69,32 @@ export class Match {
}
public get replaceString(): string {
return this.parent().parent().searchModel.replacePattern.getReplaceString(this.preview().inside);
return this.parent().parent().searchModel.replacePattern.getReplaceString(this.getMatchString());
}
public getMatchString(): string {
return this._lineText.substring(this._range.startColumn - 1, this._range.endColumn - 1);
}
}
export class FileMatch extends Disposable {
private static DecorationOption: IModelDecorationOptions = {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: 'findMatch',
overviewRuler: {
color: 'rgba(246, 185, 77, 0.7)',
darkColor: 'rgba(246, 185, 77, 0.7)',
position: OverviewRulerLane.Center
}
};
private static getDecorationOption(selected: boolean): IModelDecorationOptions {
return {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
className: selected ? 'currentFindMatch' : 'findMatch',
overviewRuler: {
color: 'rgba(246, 185, 77, 0.7)',
darkColor: 'rgba(246, 185, 77, 0.7)',
position: OverviewRulerLane.Center
}
};
}
private _onChange= this._register(new Emitter<boolean>());
private _onChange = this._register(new Emitter<boolean>());
public onChange: Event<boolean> = this._onChange.event;
private _onDispose= this._register(new Emitter<void>());
private _onDispose = this._register(new Emitter<void>());
public onDispose: Event<void> = this._onDispose.event;
private _resource: URI;
......@@ -96,16 +102,17 @@ export class FileMatch extends Disposable {
private _modelListener: IDisposable;
private _matches: SimpleMap<string, Match>;
private _removedMatches: ArraySet<string>;
private _selectedMatch: Match;
private _updateScheduler: RunOnceScheduler;
private _modelDecorations: string[] = [];
constructor(private _query: Search.IPatternInfo, private _parent: SearchResult, private rawMatch: Search.IFileMatch,
@IModelService private modelService: IModelService, @IReplaceService private replaceService: IReplaceService) {
@IModelService private modelService: IModelService, @IReplaceService private replaceService: IReplaceService) {
super();
this._resource = this.rawMatch.resource;
this._matches = new SimpleMap<string, Match>();
this._removedMatches= new ArraySet<string>();
this._removedMatches = new ArraySet<string>();
this._updateScheduler = new RunOnceScheduler(this.updateMatches.bind(this), 250);
this.createMatches();
......@@ -136,8 +143,8 @@ export class FileMatch extends Disposable {
}
private bindModel(model: IModel): void {
this._model= model;
this._modelListener= this._model.onDidChangeContent(_ => {
this._model = model;
this._modelListener = this._model.onDidChangeContent(_ => {
this._updateScheduler.schedule();
});
this._model.onWillDispose(() => this.onModelWillDispose());
......@@ -154,7 +161,7 @@ export class FileMatch extends Disposable {
if (this._model) {
this._updateScheduler.cancel();
this._model.deltaDecorations(this._modelDecorations, []);
this._model= null;
this._model = null;
this._modelListener.dispose();
}
}
......@@ -170,9 +177,12 @@ export class FileMatch extends Disposable {
.findMatches(this._query.pattern, this._model.getFullModelRange(), this._query.isRegExp, this._query.isCaseSensitive, this._query.isWordMatch);
matches.forEach(range => {
let match= new Match(this, this._model.getLineContent(range.startLineNumber), range.startLineNumber - 1, range.startColumn - 1, range.endColumn - range.startColumn);
let match = new Match(this, this._model.getLineContent(range.startLineNumber), range.startLineNumber - 1, range.startColumn - 1, range.endColumn - range.startColumn);
if (!this._removedMatches.contains(match.id())) {
this.add(match);
if (this.isMatchSelected(match)) {
this._selectedMatch = match;
}
}
});
......@@ -188,7 +198,7 @@ export class FileMatch extends Disposable {
if (this.parent().showHighlights) {
this._modelDecorations = this._model.deltaDecorations(this._modelDecorations, this.matches().map(match => <IModelDeltaDecoration>{
range: match.range(),
options: FileMatch.DecorationOption
options: FileMatch.getDecorationOption(this.isMatchSelected(match))
}));
} else {
this._modelDecorations = this._model.deltaDecorations(this._modelDecorations, []);
......@@ -208,18 +218,39 @@ export class FileMatch extends Disposable {
}
public remove(match: Match): void {
this._matches.delete(match.id());
this.removeMatch(match);
this._removedMatches.set(match.id());
this._onChange.fire(false);
}
public replace(match: Match): TPromise<any> {
return this.replaceService.replace(match).then(() => {
this._matches.delete(match.id());
this.removeMatch(match);
this._onChange.fire(false);
});
}
public setSelectedMatch(match: Match) {
if (match) {
if (!this._matches.has(match.id())) {
return;
}
if (this.isMatchSelected(match)) {
return;
}
}
this._selectedMatch = match;
this.updateHighlights();
}
public getSelectedMatch(): Match {
return this._selectedMatch;
}
public isMatchSelected(match: Match): boolean {
return this._selectedMatch && this._selectedMatch.id() === match.id();
}
public count(): number {
return this.matches().length;
}
......@@ -232,18 +263,28 @@ export class FileMatch extends Disposable {
return paths.basename(this.resource().fsPath);
}
public dispose(): void {
this.unbindModel();
this._onDispose.fire();
super.dispose();
}
public add(match: Match, trigger?: boolean) {
this._matches.set(match.id(), match);
if (trigger) {
this._onChange.fire(true);
}
}
private removeMatch(match: Match) {
this._matches.delete(match.id());
if (this.isMatchSelected(match)) {
this.setSelectedMatch(null);
} else {
this.updateHighlights();
}
}
public dispose(): void {
this.setSelectedMatch(null);
this.unbindModel();
this._onDispose.fire();
super.dispose();
}
}
export interface IChangeEvent {
......@@ -254,50 +295,50 @@ export interface IChangeEvent {
export class SearchResult extends Disposable {
private _onChange= this._register(new Emitter<IChangeEvent>());
private _onChange = this._register(new Emitter<IChangeEvent>());
public onChange: Event<IChangeEvent> = this._onChange.event;
private _fileMatches: SimpleMap<URI, FileMatch>;
private _unDisposedFileMatches: SimpleMap<URI, FileMatch>;
private _query: Search.IPatternInfo= null;
private _query: Search.IPatternInfo = null;
private _showHighlights: boolean;
private _replacingAll: boolean= false;
private _replacingAll: boolean = false;
constructor(private _searchModel: SearchModel, @IReplaceService private replaceService: IReplaceService, @ITelemetryService private telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService) {
@IInstantiationService private instantiationService: IInstantiationService) {
super();
this._fileMatches= new SimpleMap<URI, FileMatch>();
this._unDisposedFileMatches= new SimpleMap<URI, FileMatch>();
this._fileMatches = new SimpleMap<URI, FileMatch>();
this._unDisposedFileMatches = new SimpleMap<URI, FileMatch>();
}
public set query(query: Search.IPatternInfo) {
this._query= query;
this._query = query;
}
public get searchModel(): SearchModel {
return this._searchModel;
}
public add(raw: Search.IFileMatch[], silent:boolean= false): void {
public add(raw: Search.IFileMatch[], silent: boolean = false): void {
let changed: FileMatch[] = [];
raw.forEach((rawFileMatch) => {
if (!this._fileMatches.has(rawFileMatch.resource)){
let fileMatch= this.instantiationService.createInstance(FileMatch, this._query, this, rawFileMatch);
if (!this._fileMatches.has(rawFileMatch.resource)) {
let fileMatch = this.instantiationService.createInstance(FileMatch, this._query, this, rawFileMatch);
this.doAdd(fileMatch);
changed.push(fileMatch);
let disposable= fileMatch.onChange(() => this.onFileChange(fileMatch));
let disposable = fileMatch.onChange(() => this.onFileChange(fileMatch));
fileMatch.onDispose(() => disposable.dispose());
}
});
if (!silent) {
this._onChange.fire({elements: changed, added: true});
this._onChange.fire({ elements: changed, added: true });
}
}
public clear(): void {
let changed: FileMatch[]= this.matches();
let changed: FileMatch[] = this.matches();
this.disposeMatches();
this._onChange.fire({elements: changed, removed: true});
this._onChange.fire({ elements: changed, removed: true });
}
public remove(match: FileMatch): void {
......@@ -311,14 +352,14 @@ export class SearchResult extends Disposable {
}
public replaceAll(progressRunner: IProgressRunner): TPromise<any> {
this._replacingAll= true;
this._replacingAll = true;
let replaceAllTimer = this.telemetryService.timedPublicLog('replaceAll.started');
return this.replaceService.replace(this.matches(), progressRunner).then(() => {
replaceAllTimer.stop();
this._replacingAll= false;
this._replacingAll = false;
this.clear();
}, () => {
this._replacingAll= false;
this._replacingAll = false;
replaceAllTimer.stop();
});
}
......@@ -354,19 +395,19 @@ export class SearchResult extends Disposable {
}
private onFileChange(fileMatch: FileMatch): void {
let added: boolean= false;
let removed: boolean= false;
let added: boolean = false;
let removed: boolean = false;
if (!this._fileMatches.has(fileMatch.resource())) {
this.doAdd(fileMatch);
added= true;
added = true;
}
if (fileMatch.count() === 0) {
this.doRemove(fileMatch, false, false);
added= false;
removed= true;
added = false;
removed = true;
}
if (!this._replacingAll) {
this._onChange.fire({elements: [fileMatch], added: added, removed: removed});
this._onChange.fire({ elements: [fileMatch], added: added, removed: removed });
}
}
......@@ -377,7 +418,7 @@ export class SearchResult extends Disposable {
}
}
private doRemove(fileMatch: FileMatch, dispose:boolean= true, trigger: boolean= true): void {
private doRemove(fileMatch: FileMatch, dispose: boolean = true, trigger: boolean = true): void {
this._fileMatches.delete(fileMatch.resource());
if (dispose) {
fileMatch.dispose();
......@@ -386,7 +427,7 @@ export class SearchResult extends Disposable {
}
if (trigger) {
this._onChange.fire({elements: [fileMatch], removed: true});
this._onChange.fire({ elements: [fileMatch], removed: true });
}
}
......@@ -397,7 +438,7 @@ export class SearchResult extends Disposable {
this._unDisposedFileMatches.clear();
}
public dispose():void {
public dispose(): void {
this.disposeMatches();
super.dispose();
}
......@@ -406,27 +447,27 @@ export class SearchResult extends Disposable {
export class SearchModel extends Disposable {
private _searchResult: SearchResult;
private _searchQuery: ISearchQuery= null;
private _replaceActive: boolean= false;
private _replaceString: string= null;
private _replacePattern: ReplacePattern= null;
private _searchQuery: ISearchQuery = null;
private _replaceActive: boolean = false;
private _replaceString: string = null;
private _replacePattern: ReplacePattern = null;
private currentRequest: PPromise<ISearchComplete, ISearchProgressItem>;
private progressTimer: timer.ITimerEvent;
private doneTimer: timer.ITimerEvent;
private timerEvent: timer.ITimerEvent;
constructor(@ISearchService private searchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) {
constructor( @ISearchService private searchService, @ITelemetryService private telemetryService: ITelemetryService, @IInstantiationService private instantiationService: IInstantiationService) {
super();
this._searchResult= this.instantiationService.createInstance(SearchResult, this);
this._searchResult = this.instantiationService.createInstance(SearchResult, this);
}
public isReplaceActive():boolean {
public isReplaceActive(): boolean {
return this._replaceActive;
}
public set replaceActive(replaceActive: boolean) {
this._replaceActive= replaceActive;
this._replaceActive = replaceActive;
}
public get replacePattern(): ReplacePattern {
......@@ -438,13 +479,13 @@ export class SearchModel extends Disposable {
}
public set replaceString(replaceString: string) {
this._replaceString= replaceString;
this._replaceString = replaceString;
if (this._searchQuery) {
this._replacePattern= new ReplacePattern(replaceString, this._searchQuery.contentPattern);
this._replacePattern = new ReplacePattern(replaceString, this._searchQuery.contentPattern);
}
}
public get searchResult():SearchResult {
public get searchResult(): SearchResult {
return this._searchResult;
}
......@@ -452,9 +493,9 @@ export class SearchModel extends Disposable {
this.cancelSearch();
this.searchResult.clear();
this._searchQuery= query;
this._searchResult.query= this._searchQuery.contentPattern;
this._replacePattern= new ReplacePattern(this._replaceString, this._searchQuery.contentPattern);
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');
......@@ -462,8 +503,8 @@ export class SearchModel extends Disposable {
this.currentRequest = this.searchService.search(this._searchQuery);
this.currentRequest.then(value => this.onSearchCompleted(value),
e => this.onSearchError(e),
p => this.onSearchProgress(p));
e => this.onSearchError(e),
p => this.onSearchProgress(p));
return this.currentRequest;
}
......
......@@ -34,6 +34,7 @@ suite('SearchResult', () => {
assert.equal(lineMatch.range().endLineNumber, 2);
assert.equal(lineMatch.range().startColumn, 1);
assert.equal(lineMatch.range().endColumn, 4);
assert.equal('file:///c%3A/folder/file.txt>1>0foo', lineMatch.id());
});
test('Line Match - Remove', function () {
......@@ -59,6 +60,105 @@ suite('SearchResult', () => {
assert.equal(fileMatch.name(), 'file.txt');
});
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]]
}]);
testObject.setSelectedMatch(testObject.matches()[0]);
assert.equal(testObject.matches()[0], testObject.getSelectedMatch());
});
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 target = testObject.matches()[0];
testObject.remove(target);
testObject.setSelectedMatch(target);
assert.equal(undefined, testObject.getSelectedMatch());
});
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 target = testObject.matches()[0];
testObject.setSelectedMatch(target);
assert.ok(testObject.isMatchSelected(target));
});
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]]
}]);
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]]
}]);
testObject.setSelectedMatch(testObject.matches()[0]);
testObject.setSelectedMatch(null);
assert.equal(null, testObject.getSelectedMatch());
});
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]]
}]);
testObject.setSelectedMatch(null);
assert.equal(null, testObject.getSelectedMatch());
});
test('Alle Drei Zusammen', function () {
let searchResult = instantiationService.createInstance(SearchResult, null);
let fileMatch = aFileMatch('far\\boo', searchResult);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册