mainThreadEditor.ts 12.4 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  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 EditorCommon = require('vs/editor/common/editorCommon');
J
Johannes Rieken 已提交
8 9 10 11
import Event, { Emitter } from 'vs/base/common/event';
import { IEditor } from 'vs/platform/editor/common/editor';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
A
Alex Dima 已提交
12
import { Range, IRange } from 'vs/editor/common/core/range';
A
Alex Dima 已提交
13
import { Selection, ISelection } from 'vs/editor/common/core/selection';
14
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
J
Johannes Rieken 已提交
15
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
16 17
import { TextEditorCursorStyle, cursorStyleToString } from 'vs/editor/common/config/editorOptions';
import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
18
import { IResolvedTextEditorConfiguration, ISelectionChangeEvent, ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
19

J
Johannes Rieken 已提交
20
function configurationsEqual(a: IResolvedTextEditorConfiguration, b: IResolvedTextEditorConfiguration) {
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
	if (a && !b || !a && b) {
		return false;
	}
	if (!a && !b) {
		return true;
	}
	return (
		a.tabSize === b.tabSize
		&& a.insertSpaces === b.insertSpaces
	);
}

export interface IFocusTracker {
	onGainedFocus(): void;
	onLostFocus(): void;
}

/**
 * Text Editor that is permanently bound to the same model.
 * It can be bound or not to a CodeEditor.
 */
export class MainThreadTextEditor {

	private _id: string;
	private _model: EditorCommon.IModel;
	private _modelService: IModelService;
	private _modelListeners: IDisposable[];
	private _codeEditor: EditorCommon.ICommonCodeEditor;
	private _focusTracker: IFocusTracker;
	private _codeEditorListeners: IDisposable[];

	private _lastSelection: Selection[];
	private _configuration: IResolvedTextEditorConfiguration;

55
	private _onSelectionChanged: Emitter<ISelectionChangeEvent>;
56 57 58 59
	private _onConfigurationChanged: Emitter<IResolvedTextEditorConfiguration>;

	constructor(
		id: string,
J
Johannes Rieken 已提交
60 61 62
		model: EditorCommon.IModel,
		codeEditor: EditorCommon.ICommonCodeEditor,
		focusTracker: IFocusTracker,
63 64 65 66 67 68 69 70 71
		modelService: IModelService
	) {
		this._id = id;
		this._model = model;
		this._codeEditor = null;
		this._focusTracker = focusTracker;
		this._modelService = modelService;
		this._codeEditorListeners = [];

72
		this._onSelectionChanged = new Emitter<ISelectionChangeEvent>();
73 74
		this._onConfigurationChanged = new Emitter<IResolvedTextEditorConfiguration>();

J
Johannes Rieken 已提交
75
		this._lastSelection = [new Selection(1, 1, 1, 1)];
76 77 78 79 80 81
		this._modelListeners = [];
		this._modelListeners.push(this._model.onDidChangeOptions((e) => {
			this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
		}));

		this.setCodeEditor(codeEditor);
82
		this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
	}

	public dispose(): void {
		this._model = null;
		this._modelListeners = dispose(this._modelListeners);
		this._codeEditor = null;
		this._codeEditorListeners = dispose(this._codeEditorListeners);
	}

	public getId(): string {
		return this._id;
	}

	public getModel(): EditorCommon.IModel {
		return this._model;
	}

J
Johannes Rieken 已提交
100 101 102 103
	public getCodeEditor(): EditorCommon.ICommonCodeEditor {
		return this._codeEditor;
	}

J
Johannes Rieken 已提交
104
	public hasCodeEditor(codeEditor: EditorCommon.ICommonCodeEditor): boolean {
105 106 107
		return (this._codeEditor === codeEditor);
	}

J
Johannes Rieken 已提交
108
	public setCodeEditor(codeEditor: EditorCommon.ICommonCodeEditor): void {
109 110 111 112 113 114 115 116 117 118 119 120 121 122
		if (this.hasCodeEditor(codeEditor)) {
			// Nothing to do...
			return;
		}
		this._codeEditorListeners = dispose(this._codeEditorListeners);

		this._codeEditor = codeEditor;
		if (this._codeEditor) {

			// Catch early the case that this code editor gets a different model set and disassociate from this model
			this._codeEditorListeners.push(this._codeEditor.onDidChangeModel(() => {
				this.setCodeEditor(null);
			}));

123
			let forwardSelection = (event?: ICursorSelectionChangedEvent) => {
124
				this._lastSelection = this._codeEditor.getSelections();
125 126 127 128
				this._onSelectionChanged.fire({
					selections: this._lastSelection,
					source: event && event.source
				});
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
			};
			this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection(forwardSelection));
			if (!Selection.selectionsArrEqual(this._lastSelection, this._codeEditor.getSelections())) {
				forwardSelection();
			}
			this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => {
				this._focusTracker.onGainedFocus();
			}));
			this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => {
				this._focusTracker.onLostFocus();
			}));
			this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => {
				this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
			}));
			this._setConfiguration(this._readConfiguration(this._model, this._codeEditor));
		}
	}

	public isVisible(): boolean {
		return !!this._codeEditor;
	}

151
	public get onSelectionChanged(): Event<ISelectionChangeEvent> {
152 153 154 155 156 157 158 159 160 161 162 163 164 165
		return this._onSelectionChanged.event;
	}

	public get onConfigurationChanged(): Event<IResolvedTextEditorConfiguration> {
		return this._onConfigurationChanged.event;
	}

	public getSelections(): Selection[] {
		if (this._codeEditor) {
			return this._codeEditor.getSelections();
		}
		return this._lastSelection;
	}

A
Alex Dima 已提交
166
	public setSelections(selections: ISelection[]): void {
167 168 169 170 171 172 173 174 175 176 177
		if (this._codeEditor) {
			this._codeEditor.setSelections(selections);
			return;
		}
		this._lastSelection = selections.map(Selection.liftSelection);
	}

	public getConfiguration(): IResolvedTextEditorConfiguration {
		return this._configuration;
	}

J
Johannes Rieken 已提交
178
	private _setIndentConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void {
179 180 181
		if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') {
			// one of the options was set to 'auto' => detect indentation

182
			let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri);
183 184 185
			let insertSpaces = creationOpts.insertSpaces;
			let tabSize = creationOpts.tabSize;

186 187
			if (newConfiguration.insertSpaces !== 'auto' && typeof newConfiguration.insertSpaces !== 'undefined') {
				insertSpaces = newConfiguration.insertSpaces;
188
			}
189 190 191

			if (newConfiguration.tabSize !== 'auto' && typeof newConfiguration.tabSize !== 'undefined') {
				tabSize = newConfiguration.tabSize;
192 193 194 195 196 197 198 199
			}

			this._model.detectIndentation(insertSpaces, tabSize);
			return;
		}

		let newOpts: EditorCommon.ITextModelUpdateOptions = {};
		if (typeof newConfiguration.insertSpaces !== 'undefined') {
200
			newOpts.insertSpaces = newConfiguration.insertSpaces;
201 202
		}
		if (typeof newConfiguration.tabSize !== 'undefined') {
203
			newOpts.tabSize = newConfiguration.tabSize;
204 205 206 207
		}
		this._model.updateOptions(newOpts);
	}

J
Johannes Rieken 已提交
208
	public setConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void {
209 210
		this._setIndentConfiguration(newConfiguration);

211 212 213 214
		if (!this._codeEditor) {
			return;
		}

215
		if (newConfiguration.cursorStyle) {
216
			let newCursorStyle = cursorStyleToString(newConfiguration.cursorStyle);
217 218 219 220
			this._codeEditor.updateOptions({
				cursorStyle: newCursorStyle
			});
		}
221 222

		if (typeof newConfiguration.lineNumbers !== 'undefined') {
223
			let lineNumbers: 'on' | 'off' | 'relative';
224 225
			switch (newConfiguration.lineNumbers) {
				case TextEditorLineNumbersStyle.On:
226
					lineNumbers = 'on';
227 228 229 230 231
					break;
				case TextEditorLineNumbersStyle.Relative:
					lineNumbers = 'relative';
					break;
				default:
232
					lineNumbers = 'off';
233
			}
234
			this._codeEditor.updateOptions({
235
				lineNumbers: lineNumbers
236 237
			});
		}
238 239
	}

J
Johannes Rieken 已提交
240
	public setDecorations(key: string, ranges: EditorCommon.IDecorationOptions[]): void {
241 242 243 244
		if (!this._codeEditor) {
			return;
		}
		this._codeEditor.setDecorations(key, ranges);
245 246 247 248 249 250 251 252 253 254 255
	}

	public setDecorationsFast(key: string, _ranges: number[]): void {
		if (!this._codeEditor) {
			return;
		}
		let ranges: Range[] = [];
		for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) {
			ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]);
		}
		this._codeEditor.setDecorationsFast(key, ranges);
256 257
	}

A
Alex Dima 已提交
258
	public revealRange(range: IRange, revealType: TextEditorRevealType): void {
259 260 261
		if (!this._codeEditor) {
			return;
		}
262 263
		switch (revealType) {
			case TextEditorRevealType.Default:
264
				this._codeEditor.revealRange(range, EditorCommon.ScrollType.Smooth);
265 266
				break;
			case TextEditorRevealType.InCenter:
267
				this._codeEditor.revealRangeInCenter(range, EditorCommon.ScrollType.Smooth);
268
				break;
269
			case TextEditorRevealType.InCenterIfOutsideViewport:
270
				this._codeEditor.revealRangeInCenterIfOutsideViewport(range, EditorCommon.ScrollType.Smooth);
271 272
				break;
			case TextEditorRevealType.AtTop:
273
				this._codeEditor.revealRangeAtTop(range, EditorCommon.ScrollType.Smooth);
274 275
				break;
			default:
276
				console.warn(`Unknown revealType: ${revealType}`);
277
				break;
278 279 280
		}
	}

J
Johannes Rieken 已提交
281
	private _readConfiguration(model: EditorCommon.IModel, codeEditor: EditorCommon.ICommonCodeEditor): IResolvedTextEditorConfiguration {
282 283 284 285
		if (model.isDisposed()) {
			// shutdown time
			return this._configuration;
		}
286
		let cursorStyle = this._configuration ? this._configuration.cursorStyle : TextEditorCursorStyle.Line;
287
		let lineNumbers: TextEditorLineNumbersStyle = this._configuration ? this._configuration.lineNumbers : TextEditorLineNumbersStyle.On;
288 289 290
		if (codeEditor) {
			let codeEditorOpts = codeEditor.getConfiguration();
			cursorStyle = codeEditorOpts.viewInfo.cursorStyle;
291 292

			if (codeEditorOpts.viewInfo.renderRelativeLineNumbers) {
293
				lineNumbers = TextEditorLineNumbersStyle.Relative;
294
			} else if (codeEditorOpts.viewInfo.renderLineNumbers) {
295
				lineNumbers = TextEditorLineNumbersStyle.On;
296
			} else {
297
				lineNumbers = TextEditorLineNumbersStyle.Off;
298
			}
299 300 301 302 303 304
		}

		let indent = model.getOptions();
		return {
			insertSpaces: indent.insertSpaces,
			tabSize: indent.tabSize,
305 306
			cursorStyle: cursorStyle,
			lineNumbers: lineNumbers
307 308 309
		};
	}

J
Johannes Rieken 已提交
310
	private _setConfiguration(newConfiguration: IResolvedTextEditorConfiguration): void {
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
		if (configurationsEqual(this._configuration, newConfiguration)) {
			return;
		}
		this._configuration = newConfiguration;
		this._onConfigurationChanged.fire(this._configuration);
	}

	public isFocused(): boolean {
		if (this._codeEditor) {
			return this._codeEditor.isFocused();
		}
		return false;
	}

	public matches(editor: IEditor): boolean {
		if (!editor) {
			return false;
		}
		return editor.getControl() === this._codeEditor;
	}

J
Johannes Rieken 已提交
332
	public applyEdits(versionIdCheck: number, edits: EditorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): boolean {
333 334 335 336 337 338
		if (this._model.getVersionId() !== versionIdCheck) {
			// throw new Error('Model has changed in the meantime!');
			// model changed in the meantime
			return false;
		}

339 340 341 342
		if (!this._codeEditor) {
			// console.warn('applyEdits on invisible editor');
			return false;
		}
343

344 345 346 347
		if (opts.setEndOfLine === EndOfLine.CRLF) {
			this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF);
		} else if (opts.setEndOfLine === EndOfLine.LF) {
			this._model.setEOL(EditorCommon.EndOfLineSequence.LF);
348 349
		}

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
		let transformedEdits = edits.map((edit): EditorCommon.IIdentifiedSingleEditOperation => {
			return {
				identifier: null,
				range: Range.lift(edit.range),
				text: edit.text,
				forceMoveMarkers: edit.forceMoveMarkers
			};
		});

		if (opts.undoStopBefore) {
			this._codeEditor.pushUndoStop();
		}
		this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits);
		if (opts.undoStopAfter) {
			this._codeEditor.pushUndoStop();
		}
		return true;
367
	}
368

A
Alex Dima 已提交
369
	insertSnippet(template: string, ranges: IRange[], opts: IUndoStopOptions) {
370

371 372 373 374
		if (!this._codeEditor) {
			return false;
		}

375
		const snippetController = SnippetController2.get(this._codeEditor);
376

377 378
		// // cancel previous snippet mode
		// snippetController.leaveSnippet();
379 380

		// set selection, focus editor
381
		const selections = ranges.map(r => new Selection(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn));
382
		this._codeEditor.setSelections(selections);
383
		this._codeEditor.focus();
384

385
		// make modifications
386
		snippetController.insert(template, 0, 0, opts.undoStopBefore, opts.undoStopAfter);
387 388

		return true;
389
	}
390
}