提交 e086d4ec 编写于 作者: J Johannes Rieken

some refactorings in reference search, prep for #5893

上级 75ec70cc
......@@ -31,7 +31,7 @@ import {ICodeEditor} from 'vs/editor/browser/editorBrowser';
import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions';
import {IPeekViewService, getOuterEditor} from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget';
import {findReferences} from '../common/referenceSearch';
import {EventType, ReferencesModel} from './referenceSearchModel';
import {ReferencesModel, OneReference} from './referenceSearchModel';
import {ReferenceWidget, LayoutData} from './referenceSearchWidget';
import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation';
......@@ -39,19 +39,12 @@ export class FindReferencesController implements editorCommon.IEditorContributio
public static ID = 'editor.contrib.findReferencesController';
private _editor:ICodeEditor;
private _widget:ReferenceWidget;
private _requestIdPool: number;
private _callOnClear:Function[];
private _model:ReferencesModel;
private _modelRevealing:boolean;
private editorService: IEditorService;
private telemetryService: ITelemetryService;
private messageService: IMessageService;
private instantiationService: IInstantiationService;
private contextService: IWorkspaceContextService;
private peekViewService: IPeekViewService;
private _editor: ICodeEditor;
private _widget: ReferenceWidget;
private _model: ReferencesModel;
private _requestIdPool = 0;
private _callOnClear: Function[] = [];
private _ignoreModelChangeEvent = false;
private _startTime: number = -1;
private _referenceSearchVisible: IKeybindingContextKey<boolean>;
......@@ -62,24 +55,15 @@ export class FindReferencesController implements editorCommon.IEditorContributio
public constructor(
editor: ICodeEditor,
@IEditorService editorService: IEditorService,
@ITelemetryService telemetryService: ITelemetryService,
@IMessageService messageService: IMessageService,
@IInstantiationService instantiationService: IInstantiationService,
@IKeybindingService keybindingService: IKeybindingService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IEditorService private _editorService: IEditorService,
@ITelemetryService private _telemetryService: ITelemetryService,
@IMessageService private _messageService: IMessageService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
@IStorageService private _storageService: IStorageService,
@optional(IPeekViewService) peekViewService: IPeekViewService
@optional(IPeekViewService) private _peekViewService: IPeekViewService
) {
this._requestIdPool = 0;
this._callOnClear = [];
this.editorService = editorService;
this.telemetryService = telemetryService;
this.messageService = messageService;
this.instantiationService = instantiationService;
this.peekViewService = peekViewService;
this.contextService = contextService;
this._modelRevealing = false;
this._editor = editor;
this._referenceSearchVisible = keybindingService.createKey(CONTEXT_REFERENCE_SEARCH_VISIBLE, false);
}
......@@ -97,7 +81,7 @@ export class FindReferencesController implements editorCommon.IEditorContributio
}
public isInPeekView() : boolean {
return this.peekViewService && this.peekViewService.isActive;
return this._peekViewService && this._peekViewService.isActive;
}
public closeReferenceSearch(): void {
......@@ -120,13 +104,13 @@ export class FindReferencesController implements editorCommon.IEditorContributio
// close the widget on model/mode changes
this._callOnClear.push(this._editor.addListener(editorCommon.EventType.ModelModeChanged, () => { this.clear(); }));
this._callOnClear.push(this._editor.addListener(editorCommon.EventType.ModelChanged, () => {
if(!this._modelRevealing) {
if(!this._ignoreModelChangeEvent) {
this.clear();
}
}));
const storageKey = 'peekViewLayout';
const data = <LayoutData> JSON.parse(this._storageService.get(storageKey, undefined, '{}'));
this._widget = new ReferenceWidget(this._editor, data, this.editorService, this.contextService, this.instantiationService);
this._widget = new ReferenceWidget(this._editor, data, this._editorService, this._contextService, this._instantiationService);
this._widget.setTitle(nls.localize('labelLoading', "Loading..."));
this._widget.show(range);
this._callOnClear.push(this._widget.onDidClose(() => {
......@@ -137,31 +121,27 @@ export class FindReferencesController implements editorCommon.IEditorContributio
this.clear();
}).dispose);
this._callOnClear.push(this._widget.onDidDoubleClick(event => {
if(!event.reference) {
return;
}
// open editor
this.editorService.openEditor({
resource: event.reference,
options: { selection: event.range }
}, event.originalEvent.ctrlKey || event.originalEvent.metaKey).done(null, onUnexpectedError);
// close zone
if (!(event.originalEvent.ctrlKey || event.originalEvent.metaKey)) {
this.clear();
this._callOnClear.push(this._widget.onDidSelectReference(event => {
let {element, kind} = event;
switch (kind) {
case 'side':
case 'open':
this._openReference(element, kind === 'side');
break;
case 'goto':
this._gotoReference(element);
break;
}
}).dispose);
var requestId = ++this._requestIdPool,
editorModel = this._editor.getModel();
var timer = this.telemetryService.timedPublicLog('findReferences', {
var timer = this._telemetryService.timedPublicLog('findReferences', {
mode: editorModel.getMode().getId()
});
referencesPromise.then((references:IReference[]) => {
referencesPromise.then(references => {
// still current request? widget still open?
if(requestId !== this._requestIdPool || !this._widget) {
......@@ -177,38 +157,7 @@ export class FindReferencesController implements editorCommon.IEditorContributio
}
// create result model
this._model = new ReferencesModel(references, this.editorService);
this._model.currentReference = this._model.findReference(editorModel.getAssociatedResource(), range.getStartPosition());
var unbind = this._model.addListener(EventType.CurrentReferenceChanged, () => {
this._modelRevealing = true;
this.editorService.openEditor({
resource: this._model.currentReference.resource,
options: { selection: this._model.currentReference.range }
}).done((openedEditor) => {
if(!openedEditor || openedEditor.getControl() !== this._editor) {
// TODO@Alex TODO@Joh
// when opening the current reference we might end up
// in a different editor instance. that means we also have
// a different instance of this reference search controller
// and cannot hold onto the widget (which likely doesn't
// exist). Instead of bailing out we should find the
// 'sister' action and pass our current model on to it.
this.clear();
return;
}
this._modelRevealing = false;
this._widget.show(this._model.currentReference.range);
this._widget.focus();
}, (err) => {
this._modelRevealing = false;
onUnexpectedError(err);
});
});
this._callOnClear.push(unbind);
this._model = new ReferencesModel(references, this._editorService);
// show widget
this._startTime = Date.now();
......@@ -219,17 +168,17 @@ export class FindReferencesController implements editorCommon.IEditorContributio
timer.stop();
}, (error:any) => {
this.messageService.show(Severity.Error, error);
this._messageService.show(Severity.Error, error);
timer.stop();
});
return this._widget;
}
private clear():boolean {
private clear(): boolean {
if (this._startTime !== -1) {
this.telemetryService.publicLog('zoneWidgetShown', {
this._telemetryService.publicLog('zoneWidgetShown', {
mode: 'reference search',
elapsedTime: Date.now() - this._startTime
});
......@@ -237,7 +186,7 @@ export class FindReferencesController implements editorCommon.IEditorContributio
}
var result = false;
if(this._widget) {
if (this._widget) {
this._widget.dispose();
this._widget = null;
result = true;
......@@ -254,8 +203,50 @@ export class FindReferencesController implements editorCommon.IEditorContributio
return result;
}
}
private _gotoReference(ref: OneReference): void {
this._ignoreModelChangeEvent = true;
const {resource, range} = ref;
this._editorService.openEditor({
resource,
options: { selection: range }
}).done(openedEditor => {
this._ignoreModelChangeEvent = false;
if (!openedEditor || openedEditor.getControl() !== this._editor) {
// TODO@Alex TODO@Joh
// when opening the current reference we might end up
// in a different editor instance. that means we also have
// a different instance of this reference search controller
// and cannot hold onto the widget (which likely doesn't
// exist). Instead of bailing out we should find the
// 'sister' action and pass our current model on to it.
this.clear();
return;
}
this._widget.show(range);
this._widget.focus();
}, (err) => {
this._ignoreModelChangeEvent = false;
onUnexpectedError(err);
});
}
private _openReference(ref: OneReference, sideBySide: boolean): void {
const {resource, range} = ref;
this._editorService.openEditor({
resource,
options: { selection: range }
}, sideBySide);
// clear stage
if (!sideBySide) {
this.clear();
}
}
}
export class ReferenceAction extends EditorAction {
......
......@@ -6,6 +6,7 @@
import * as collections from 'vs/base/common/collections';
import {EventEmitter} from 'vs/base/common/eventEmitter';
import Event, {fromEventEmitter} from 'vs/base/common/event';
import {basename, dirname} from 'vs/base/common/paths';
import * as strings from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
......@@ -16,19 +17,16 @@ import {Range} from 'vs/editor/common/core/range';
import {IModel, IPosition, IRange} from 'vs/editor/common/editorCommon';
import {IReference} from 'vs/editor/common/modes';
export namespace EventType {
export var OnReferenceRangeChanged = 'refrence.rangeChanged';
export var CurrentReferenceChanged = 'reference.currentChanged';
}
export class OneReference {
private _id: string;
private _range: IRange;
constructor(private _parent: FileReferences, reference: IReference) {
constructor(
private _parent: FileReferences,
private _range: IRange,
private _eventBus: EventEmitter
) {
this._id = generateUuid();
this._range = reference.range;
}
public get id(): string {
......@@ -61,7 +59,7 @@ export class OneReference {
public set range(value: IRange) {
this._range = value;
this.parent.parent.emit(EventType.OnReferenceRangeChanged, this);
this._eventBus.emit('ref/changed', this);
}
}
......@@ -145,16 +143,14 @@ export class FileReferences {
}
}
export class ReferencesModel extends EventEmitter {
export class ReferencesModel {
private _references: FileReferences[];
private _currentReference: OneReference;
private _eventBus = new EventEmitter();
onDidChangeReferenceRange: Event<OneReference> = fromEventEmitter<OneReference>(this._eventBus, 'ref/changed');
constructor(references: IReference[], editorService: IEditorService) {
super([
EventType.CurrentReferenceChanged,
EventType.OnReferenceRangeChanged
]);
let referencesByFile: { [n: string]: FileReferences } = Object.create(null);
let seen: { [n: string]: boolean } = Object.create(null);
......@@ -169,7 +165,7 @@ export class ReferencesModel extends EventEmitter {
let fileReferences = new FileReferences(this, resource, editorService);
fileReferences = collections.lookupOrInsert(referencesByFile, fileReferences.id, fileReferences);
fileReferences.children.push(new OneReference(fileReferences, reference));
fileReferences.children.push(new OneReference(fileReferences, reference.range, this._eventBus));
}
});
......@@ -181,15 +177,6 @@ export class ReferencesModel extends EventEmitter {
return this._references;
}
public get currentReference(): OneReference {
return this._currentReference;
}
public set currentReference(reference: OneReference) {
this._currentReference = reference;
this.emit(EventType.CurrentReferenceChanged, this);
}
public nextReference(reference: OneReference): OneReference {
var idx = reference.parent.children.indexOf(reference),
......
......@@ -13,7 +13,6 @@ import Event, {Emitter} from 'vs/base/common/event';
import {IDisposable, cAll, dispose, Disposables} from 'vs/base/common/lifecycle';
import {Schemas} from 'vs/base/common/network';
import * as strings from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {$, Builder} from 'vs/base/browser/builder';
import * as dom from 'vs/base/browser/dom';
......@@ -37,7 +36,7 @@ import {Model} from 'vs/editor/common/model/model';
import {ICodeEditor, IMouseTarget} from 'vs/editor/browser/editorBrowser';
import {EmbeddedCodeEditorWidget} from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import {PeekViewWidget, IPeekViewService} from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget';
import {EventType, FileReferences, OneReference, ReferencesModel} from './referenceSearchModel';
import {FileReferences, OneReference, ReferencesModel} from './referenceSearchModel';
class DecorationsManager implements IDisposable {
......@@ -433,6 +432,11 @@ export interface LayoutData {
heightInLines: number;
}
export interface SelectionEvent {
kind: 'goto' | 'show' | 'side' | 'open';
element: OneReference;
}
/**
* ZoneWidget that is shown inside the editor
*/
......@@ -440,10 +444,11 @@ export class ReferenceWidget extends PeekViewWidget {
public static INNER_EDITOR_CONTEXT_KEY = 'inReferenceSearchEditor';
private _decorationsManager: DecorationsManager;
private _model: ReferencesModel;
private _callOnModel: IDisposable[] = [];
private _onDidDoubleClick = new Emitter<{ reference: URI, range: Range, originalEvent: MouseEvent }>();
private _decorationsManager: DecorationsManager;
private _disposeOnNewModel: IDisposable[] = [];
private _onDidSelectReference = new Emitter<SelectionEvent>();
private _tree: Tree;
private _treeContainer: Builder;
......@@ -472,23 +477,26 @@ export class ReferenceWidget extends PeekViewWidget {
super.dispose();
}
get onDidDoubleClick():Event<{ reference: URI, range: Range, originalEvent: MouseEvent }> {
return this._onDidDoubleClick.event;
get onDidSelectReference(): Event<SelectionEvent> {
return this._onDidSelectReference.event;
}
show(where: editorCommon.IRange) {
this.editor.revealRangeInCenterIfOutsideViewport(where);
super.show(where, this.layoutData.heightInLines || 18);
}
focus(): void {
this._tree.DOMFocus();
}
protected _onTitleClick(e: MouseEvent): void {
if (!this._preview || !this._preview.getModel()) {
return;
if (this._preview && this._preview.getModel()) {
this._onDidSelectReference.fire({
element: this._getFocusedReference(),
kind: e.ctrlKey || e.metaKey ? 'side' : 'open'
});
}
var model = this._preview.getModel(),
lineNumber = this._preview.getPosition().lineNumber,
titleRange = new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber));
this._onDidDoubleClick.fire({ reference: this._getFocusedReference(), range: titleRange, originalEvent: e });
}
protected _fillBody(containerElement: HTMLElement): void {
......@@ -574,65 +582,56 @@ export class ReferenceWidget extends PeekViewWidget {
this._preview.layout();
}
public showMessage(message: string): void {
this.setTitle('');
this._messageContainer.innerHtml(message).show();
}
public setModel(newModel: ReferencesModel): void {
// clean up
this._callOnModel = dispose(this._callOnModel);
this._disposeOnNewModel = dispose(this._disposeOnNewModel);
this._model = newModel;
if (this._model) {
this._onNewModel();
}
}
public showMessage(message: string): void {
this.setTitle('');
this._messageContainer.innerHtml(message).show();
}
private _onNewModel(): void {
this._messageContainer.hide();
this._decorationsManager = new DecorationsManager(this._preview, this._model);
this._callOnModel.push(this._decorationsManager);
this._disposeOnNewModel.push(this._decorationsManager);
// listen on model changes
this._callOnModel.push(this._model.addListener2(EventType.OnReferenceRangeChanged, (reference: OneReference) => {
this._tree.refresh(reference);
}));
this._disposeOnNewModel.push(this._model.onDidChangeReferenceRange(reference => this._tree.refresh(reference)));
// listen on selection and focus
this._callOnModel.push(this._tree.addListener2(Controller.Events.FOCUSED, (element) => {
this._disposeOnNewModel.push(this._tree.addListener2(Controller.Events.FOCUSED, (element) => {
if (element instanceof OneReference) {
this._showReferencePreview(element);
this._onDidSelectReference.fire({ element, kind: 'show' });
this._revealReference(element);
}
}));
this._callOnModel.push(this._tree.addListener2(Controller.Events.SELECTED, (element: any) => {
this._disposeOnNewModel.push(this._tree.addListener2(Controller.Events.SELECTED, (element: any) => {
if (element instanceof OneReference) {
this._showReferencePreview(element);
this._model.currentReference = element;
this._onDidSelectReference.fire({ element, kind: 'goto' });
this._revealReference(element);
}
}));
this._callOnModel.push(this._tree.addListener2(Controller.Events.OPEN_TO_SIDE, (element: any) => {
this._disposeOnNewModel.push(this._tree.addListener2(Controller.Events.OPEN_TO_SIDE, (element: any) => {
if (element instanceof OneReference) {
this._editorService.openEditor({
resource: (<OneReference>element).resource,
options: {
selection: element.range
}
}, true);
this._onDidSelectReference.fire({ element, kind: 'side' });
}
}));
var input = this._model.children.length === 1 ? <any>this._model.children[0] : <any>this._model;
this._tree.setInput(input).then(() => {
this._tree.setSelection([this._model.currentReference]);
}).done(null, onUnexpectedError);
// listen on editor
this._callOnModel.push(this._preview.addListener2(editorCommon.EventType.MouseDown, (e: { event: MouseEvent; target: IMouseTarget; }) => {
this._disposeOnNewModel.push(this._preview.addListener2(editorCommon.EventType.MouseDown, (e: { event: MouseEvent; target: IMouseTarget; }) => {
if (e.event.detail === 2) {
this._onDidDoubleClick.fire({ reference: this._getFocusedReference(), range: e.target.range, originalEvent: e.event });
this._onDidSelectReference.fire({
element: this._getFocusedReference(),
kind: (e.event.ctrlKey || e.event.metaKey) ? 'side' : 'open'
});
}
}));
......@@ -644,28 +643,31 @@ export class ReferenceWidget extends PeekViewWidget {
this._tree.layout();
this.focus();
// preview the current reference
this._showReferencePreview(this._model.nextReference(this._model.currentReference));
// pick input and a reference to begin with
const input = this._model.children.length === 1 ? this._model.children[0] : this._model;
const selection = this._model.children[0].children[0];
this._tree.setInput(input).then(() => this._revealReference(selection));
}
private _getFocusedReference(): URI {
var element = this._tree.getFocus();
private _getFocusedReference(): OneReference {
const element = this._tree.getFocus();
if (element instanceof OneReference) {
return (<OneReference>element).resource;
return element;
} else if (element instanceof FileReferences) {
var referenceFile = (<FileReferences>element);
if (referenceFile.children.length > 0) {
return referenceFile.children[0].resource;
if (element.children.length > 0) {
return element.children[0];
}
}
return null;
}
public focus(): void {
this._tree.DOMFocus();
}
private _revealReference(reference: OneReference): void {
private _showReferencePreview(reference: OneReference): void {
// Update widget header
if (reference.resource.scheme !== Schemas.inMemory) {
this.setTitle(reference.name, getPathLabel(reference.directory, this._contextService));
} else {
this.setTitle(nls.localize('peekView.alternateTitle', "References"));
}
// show in editor
this._editorService.resolveEditorModel({ resource: reference.resource }).done((model) => {
......@@ -679,22 +681,12 @@ export class ReferenceWidget extends PeekViewWidget {
this._preview.setModel(this._previewNotAvailableMessage);
}
// Update widget header
if (reference.resource.scheme !== Schemas.inMemory) {
this.setTitle(reference.name, getPathLabel(reference.directory, this._contextService));
} else {
this.setTitle(nls.localize('peekView.alternateTitle', "References"));
}
}, onUnexpectedError);
// show in tree
this._tree.reveal(reference)
.then(() => {
this._tree.setSelection([reference]);
this._tree.setFocus(reference);
})
.done(null, onUnexpectedError);
this._tree.reveal(reference).then(() => {
this._tree.setSelection([reference]);
this._tree.setFocus(reference);
});
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册