referencesController.ts 8.0 KB
Newer Older
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  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 nls from 'vs/nls';
import {onUnexpectedError} from 'vs/base/common/errors';
9
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
10 11 12 13 14 15 16
import Severity from 'vs/base/common/severity';
import {TPromise} from 'vs/base/common/winjs.base';
import {IEditorService} from 'vs/platform/editor/common/editor';
import {IInstantiationService, optional} from 'vs/platform/instantiation/common/instantiation';
import {IKeybindingContextKey, IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {IMessageService} from 'vs/platform/message/common/message';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
17
import {IConfigurationService, getConfigurationValue} from 'vs/platform/configuration/common/configuration';
18 19 20 21 22 23 24 25
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IStorageService} from 'vs/platform/storage/common/storage';
import * as editorCommon from 'vs/editor/common/editorCommon';
import {ICodeEditor} from 'vs/editor/browser/editorBrowser';
import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions';
import {IPeekViewService} from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget';
import {ReferencesModel, OneReference} from './referencesModel';
import {ReferenceWidget, LayoutData} from './referencesWidget';
26
import {Range} from 'vs/editor/common/core/range';
27

28
export const ctxReferenceSearchVisible = 'referenceSearchVisible';
29 30

export interface RequestOptions {
31 32
	getMetaTitle(model: ReferencesModel): string;
	onGoto?: (reference: OneReference) => TPromise<any>;
33 34 35 36 37 38 39 40 41 42
}

export class ReferencesController implements editorCommon.IEditorContribution {

	public static ID = 'editor.contrib.referencesController';

	private _editor: ICodeEditor;
	private _widget: ReferenceWidget;
	private _model: ReferencesModel;
	private _requestIdPool = 0;
43
	private _disposables: IDisposable[] = [];
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
	private _ignoreModelChangeEvent = false;

	private _referenceSearchVisible: IKeybindingContextKey<boolean>;

	static getController(editor:editorCommon.ICommonCodeEditor): ReferencesController {
		return <ReferencesController> editor.getContribution(ReferencesController.ID);
	}

	public constructor(
		editor: ICodeEditor,
		@IKeybindingService keybindingService: IKeybindingService,
		@IEditorService private _editorService: IEditorService,
		@ITelemetryService private _telemetryService: ITelemetryService,
		@IMessageService private _messageService: IMessageService,
		@IInstantiationService private _instantiationService: IInstantiationService,
		@IWorkspaceContextService private _contextService: IWorkspaceContextService,
		@IStorageService private _storageService: IStorageService,
61
		@IConfigurationService private _configurationService: IConfigurationService,
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
		@optional(IPeekViewService) private _peekViewService: IPeekViewService
	) {
		this._editor = editor;
		this._referenceSearchVisible = keybindingService.createKey(ctxReferenceSearchVisible, false);
	}

	public getId(): string {
		return ReferencesController.ID;
	}

	public dispose(): void {
		if (this._widget) {
			this._widget.dispose();
			this._widget = null;
		}
		this._editor = null;
	}

80
	public toggleWidget(range: Range, modelPromise: TPromise<ReferencesModel>, options: RequestOptions) : void {
81

82 83 84 85 86 87 88
		// close current widget and return early is position didn't change
		let widgetPosition: editorCommon.IPosition;
		if (this._widget) {
			widgetPosition = this._widget.position;
		}
		this.closeWidget();
		if(!!widgetPosition && range.containsPosition(widgetPosition)) {
89 90 91 92 93 94
			return null;
		}

		this._referenceSearchVisible.set(true);

		// close the widget on model/mode changes
A
Alex Dima 已提交
95 96
		this._disposables.push(this._editor.onDidModelModeChange(() => { this.closeWidget(); }));
		this._disposables.push(this._editor.onDidModelChange(() => {
97
			if(!this._ignoreModelChangeEvent) {
98
				this.closeWidget();
99 100 101 102 103 104 105
			}
		}));
		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.setTitle(nls.localize('labelLoading', "Loading..."));
		this._widget.show(range);
106 107
		this._disposables.push(this._widget.onDidClose(() => {
			modelPromise.cancel();
108 109 110

			this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData));
			this._widget = null;
111 112
			this.closeWidget();
		}));
113

114
		this._disposables.push(this._widget.onDidSelectReference(event => {
115 116 117
			let {element, kind} = event;
			switch (kind) {
				case 'open':
118 119 120 121 122 123 124 125
					if (event.source === 'editor'
						&& getConfigurationValue(this._configurationService.getConfiguration(), 'editor.stablePeek', false)) {

						// when stable peek is configured we don't close
						// the peek window on selecting the editor 
						break;
					}
				case 'side':
126 127 128 129 130 131 132 133 134 135
					this._openReference(element, kind === 'side');
					break;
				case 'goto':
					if (options.onGoto) {
						options.onGoto(element);
					} else {
						this._gotoReference(element);
					}
					break;
			}
136
		}));
137

138 139 140
		const requestId = ++this._requestIdPool;
		const timer = this._telemetryService.timedPublicLog('findReferences', {
			mode: this._editor.getModel().getMode().getId()
141 142
		});

143
		modelPromise.then(model => {
144 145

			// still current request? widget still open?
146
			if (requestId !== this._requestIdPool || !this._widget) {
147 148
				return;
			}
149 150 151 152 153 154 155 156 157 158 159 160
			this._model = model;

			// measure time it stays open
			const startTime = Date.now();
			this._disposables.push({
				dispose: () => {
					this._telemetryService.publicLog('zoneWidgetShown', {
						mode: 'reference search',
						elapsedTime: Date.now() - startTime
					});
				}
			});
161

162 163
			// show widget
			return this._widget.setModel(this._model).then(() => {
164

165 166
				// set title
				this._widget.setMetaTitle(options.getMetaTitle(model));
167

168
				// set 'best' selection
169
				let uri = this._editor.getModel().uri;
170 171 172 173
				let pos = { lineNumber: range.startLineNumber, column: range.startColumn };
				let selection = this._model.nearestReference(uri, pos);
				return this._widget.setSelection(selection);
			});
174

175
		}, error => {
176
			this._messageService.show(Severity.Error, error);
177 178

		}).done(() => {
179 180 181 182
			timer.stop();
		});
	}

183
	public closeWidget(): void {
184 185 186 187 188
		if (this._widget) {
			this._widget.dispose();
			this._widget = null;
		}
		this._referenceSearchVisible.reset();
189
		this._disposables = dispose(this._disposables);
190 191 192 193 194 195 196
		this._model = null;
		this._editor.focus();
		this._requestIdPool += 1; // Cancel pending requests
	}

	private _gotoReference(ref: OneReference): void {
		this._ignoreModelChangeEvent = true;
197
		const {uri, range} = ref;
198 199

		this._editorService.openEditor({
200
			resource: uri,
201 202 203 204 205 206 207 208 209 210 211 212
			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.
213
				this.closeWidget();
214 215 216 217 218 219 220 221 222 223 224 225 226
				return;
			}

			this._widget.show(range);
			this._widget.focus();

		}, (err) => {
			this._ignoreModelChangeEvent = false;
			onUnexpectedError(err);
		});
	}

	private _openReference(ref: OneReference, sideBySide: boolean): void {
227
		const {uri, range} = ref;
228
		this._editorService.openEditor({
229
			resource: uri,
230 231 232 233 234
			options: { selection: range }
		}, sideBySide);

		// clear stage
		if (!sideBySide) {
235
			this.closeWidget();
236 237 238 239 240 241
		}
	}
}


EditorBrowserRegistry.registerEditorContribution(ReferencesController);