mainThreadEditor.ts 15.1 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import * as editorCommon from 'vs/editor/common/editorCommon';
M
Matt Bierner 已提交
8
import { Event, Emitter } from 'vs/base/common/event';
J
Johannes Rieken 已提交
9 10 11
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
import { TextEditorCursorStyle, cursorStyleToString, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
17
import { IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions, IEditorPropertiesChangeData } from 'vs/workbench/api/node/extHost.protocol';
18
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
A
Alex Dima 已提交
19
import { ITextModel, ISingleEditOperation, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModelUpdateOptions } from 'vs/editor/common/model';
20

21 22 23 24 25 26 27 28 29 30
export interface IFocusTracker {
	onGainedFocus(): void;
	onLostFocus(): void;
}

export class MainThreadTextEditorProperties {

	public static readFromEditor(previousProperties: MainThreadTextEditorProperties, model: ITextModel, codeEditor: ICodeEditor): MainThreadTextEditorProperties {
		const selections = MainThreadTextEditorProperties._readSelectionsFromCodeEditor(previousProperties, codeEditor);
		const options = MainThreadTextEditorProperties._readOptionsFromCodeEditor(previousProperties, model, codeEditor);
31 32
		const visibleRanges = MainThreadTextEditorProperties._readVisibleRangesFromCodeEditor(previousProperties, codeEditor);
		return new MainThreadTextEditorProperties(selections, options, visibleRanges);
33
	}
34 35

	private static _readSelectionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties, codeEditor: ICodeEditor): Selection[] {
A
Alex Dima 已提交
36
		let result: Selection[] = null;
37
		if (codeEditor) {
A
Alex Dima 已提交
38
			result = codeEditor.getSelections();
39
		}
A
Alex Dima 已提交
40 41
		if (!result && previousProperties) {
			result = previousProperties.selections;
42
		}
A
Alex Dima 已提交
43 44 45 46
		if (!result) {
			result = [new Selection(1, 1, 1, 1)];
		}
		return result;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	}

	private static _readOptionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties, model: ITextModel, codeEditor: ICodeEditor): IResolvedTextEditorConfiguration {
		if (model.isDisposed()) {
			// shutdown time
			return previousProperties.options;
		}

		let cursorStyle: TextEditorCursorStyle;
		let lineNumbers: TextEditorLineNumbersStyle;
		if (codeEditor) {
			const codeEditorOpts = codeEditor.getConfiguration();
			cursorStyle = codeEditorOpts.viewInfo.cursorStyle;

			switch (codeEditorOpts.viewInfo.renderLineNumbers) {
				case RenderLineNumbersType.Off:
					lineNumbers = TextEditorLineNumbersStyle.Off;
					break;
				case RenderLineNumbersType.Relative:
					lineNumbers = TextEditorLineNumbersStyle.Relative;
					break;
				default:
					lineNumbers = TextEditorLineNumbersStyle.On;
					break;
			}
		} else if (previousProperties) {
			cursorStyle = previousProperties.options.cursorStyle;
			lineNumbers = previousProperties.options.lineNumbers;
		} else {
			cursorStyle = TextEditorCursorStyle.Line;
			lineNumbers = TextEditorLineNumbersStyle.On;
		}

		const modelOptions = model.getOptions();
		return {
			insertSpaces: modelOptions.insertSpaces,
			tabSize: modelOptions.tabSize,
			cursorStyle: cursorStyle,
			lineNumbers: lineNumbers
		};
	}

89 90 91 92 93 94 95
	private static _readVisibleRangesFromCodeEditor(previousProperties: MainThreadTextEditorProperties, codeEditor: ICodeEditor): Range[] {
		if (codeEditor) {
			return codeEditor.getVisibleRanges();
		}
		return [];
	}

96 97
	constructor(
		public readonly selections: Selection[],
98 99
		public readonly options: IResolvedTextEditorConfiguration,
		public readonly visibleRanges: Range[]
100 101 102 103
	) {
	}

	public generateDelta(oldProps: MainThreadTextEditorProperties, selectionChangeSource: string): IEditorPropertiesChangeData {
104
		let delta: IEditorPropertiesChangeData = {
105
			options: null,
106 107
			selections: null,
			visibleRanges: null
108 109 110 111 112 113 114 115 116 117 118 119 120
		};

		if (!oldProps || !MainThreadTextEditorProperties._selectionsEqual(oldProps.selections, this.selections)) {
			delta.selections = {
				selections: this.selections,
				source: selectionChangeSource
			};
		}

		if (!oldProps || !MainThreadTextEditorProperties._optionsEqual(oldProps.options, this.options)) {
			delta.options = this.options;
		}

121 122 123 124
		if (!oldProps || !MainThreadTextEditorProperties._rangesEqual(oldProps.visibleRanges, this.visibleRanges)) {
			delta.visibleRanges = this.visibleRanges;
		}

125
		if (delta.selections || delta.options || delta.visibleRanges) {
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
			// something changed
			return delta;
		}
		// nothing changed
		return null;
	}

	private static _selectionsEqual(a: Selection[], b: Selection[]): boolean {
		if (a.length !== b.length) {
			return false;
		}
		for (let i = 0; i < a.length; i++) {
			if (!a[i].equalsSelection(b[i])) {
				return false;
			}
		}
142 143 144
		return true;
	}

145 146 147 148 149 150 151 152 153 154 155 156
	private static _rangesEqual(a: Range[], b: Range[]): boolean {
		if (a.length !== b.length) {
			return false;
		}
		for (let i = 0; i < a.length; i++) {
			if (!a[i].equalsRange(b[i])) {
				return false;
			}
		}
		return true;
	}

157 158 159 160 161 162 163 164 165 166 167 168 169 170
	private static _optionsEqual(a: IResolvedTextEditorConfiguration, b: IResolvedTextEditorConfiguration): boolean {
		if (a && !b || !a && b) {
			return false;
		}
		if (!a && !b) {
			return true;
		}
		return (
			a.tabSize === b.tabSize
			&& a.insertSpaces === b.insertSpaces
			&& a.cursorStyle === b.cursorStyle
			&& a.lineNumbers === b.lineNumbers
		);
	}
171 172 173 174 175 176 177 178 179
}

/**
 * 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;
A
Alex Dima 已提交
180
	private _model: ITextModel;
181 182
	private _modelService: IModelService;
	private _modelListeners: IDisposable[];
183
	private _codeEditor: ICodeEditor;
184 185 186
	private _focusTracker: IFocusTracker;
	private _codeEditorListeners: IDisposable[];

187
	private _properties: MainThreadTextEditorProperties;
M
Matt Bierner 已提交
188
	private readonly _onPropertiesChanged: Emitter<IEditorPropertiesChangeData>;
189 190 191

	constructor(
		id: string,
A
Alex Dima 已提交
192
		model: ITextModel,
193
		codeEditor: ICodeEditor,
J
Johannes Rieken 已提交
194
		focusTracker: IFocusTracker,
195 196 197 198 199 200 201 202 203
		modelService: IModelService
	) {
		this._id = id;
		this._model = model;
		this._codeEditor = null;
		this._focusTracker = focusTracker;
		this._modelService = modelService;
		this._codeEditorListeners = [];

204 205
		this._properties = null;
		this._onPropertiesChanged = new Emitter<IEditorPropertiesChangeData>();
206 207 208

		this._modelListeners = [];
		this._modelListeners.push(this._model.onDidChangeOptions((e) => {
209
			this._updatePropertiesNow(null);
210 211 212
		}));

		this.setCodeEditor(codeEditor);
213
		this._updatePropertiesNow(null);
214 215 216 217 218 219 220 221 222
	}

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

223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
	private _updatePropertiesNow(selectionChangeSource: string): void {
		this._setProperties(
			MainThreadTextEditorProperties.readFromEditor(this._properties, this._model, this._codeEditor),
			selectionChangeSource
		);
	}

	private _setProperties(newProperties: MainThreadTextEditorProperties, selectionChangeSource: string): void {
		const delta = newProperties.generateDelta(this._properties, selectionChangeSource);
		this._properties = newProperties;
		if (delta) {
			this._onPropertiesChanged.fire(delta);
		}
	}

238 239 240 241
	public getId(): string {
		return this._id;
	}

A
Alex Dima 已提交
242
	public getModel(): ITextModel {
243 244 245
		return this._model;
	}

246
	public getCodeEditor(): ICodeEditor {
J
Johannes Rieken 已提交
247 248 249
		return this._codeEditor;
	}

250
	public hasCodeEditor(codeEditor: ICodeEditor): boolean {
251 252 253
		return (this._codeEditor === codeEditor);
	}

254
	public setCodeEditor(codeEditor: ICodeEditor): void {
255 256 257 258 259 260 261 262 263 264 265 266 267 268
		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);
			}));

A
Alex Dima 已提交
269
			this._codeEditorListeners.push(this._codeEditor.onDidFocusEditorWidget(() => {
270 271
				this._focusTracker.onGainedFocus();
			}));
A
Alex Dima 已提交
272
			this._codeEditorListeners.push(this._codeEditor.onDidBlurEditorWidget(() => {
273 274
				this._focusTracker.onLostFocus();
			}));
275 276

			this._codeEditorListeners.push(this._codeEditor.onDidChangeCursorSelection((e) => {
277
				// selection
278 279
				this._updatePropertiesNow(e.source);
			}));
280
			this._codeEditorListeners.push(this._codeEditor.onDidChangeConfiguration(() => {
281 282 283 284 285 286 287 288 289
				// options
				this._updatePropertiesNow(null);
			}));
			this._codeEditorListeners.push(this._codeEditor.onDidLayoutChange(() => {
				// visibleRanges
				this._updatePropertiesNow(null);
			}));
			this._codeEditorListeners.push(this._codeEditor.onDidScrollChange(() => {
				// visibleRanges
290
				this._updatePropertiesNow(null);
291
			}));
292
			this._updatePropertiesNow(null);
293 294 295 296 297 298 299
		}
	}

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

300 301
	public getProperties(): MainThreadTextEditorProperties {
		return this._properties;
302 303
	}

304 305
	public get onPropertiesChanged(): Event<IEditorPropertiesChangeData> {
		return this._onPropertiesChanged.event;
306 307
	}

A
Alex Dima 已提交
308
	public setSelections(selections: ISelection[]): void {
309 310 311 312 313
		if (this._codeEditor) {
			this._codeEditor.setSelections(selections);
			return;
		}

314 315
		const newSelections = selections.map(Selection.liftSelection);
		this._setProperties(
316
			new MainThreadTextEditorProperties(newSelections, this._properties.options, this._properties.visibleRanges),
317 318
			null
		);
319 320
	}

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

325
			let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri, this._model.isForSimpleWidget);
326 327 328
			let insertSpaces = creationOpts.insertSpaces;
			let tabSize = creationOpts.tabSize;

329 330
			if (newConfiguration.insertSpaces !== 'auto' && typeof newConfiguration.insertSpaces !== 'undefined') {
				insertSpaces = newConfiguration.insertSpaces;
331
			}
332 333 334

			if (newConfiguration.tabSize !== 'auto' && typeof newConfiguration.tabSize !== 'undefined') {
				tabSize = newConfiguration.tabSize;
335 336 337 338 339 340
			}

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

341
		let newOpts: ITextModelUpdateOptions = {};
342
		if (typeof newConfiguration.insertSpaces !== 'undefined') {
343
			newOpts.insertSpaces = newConfiguration.insertSpaces;
344 345
		}
		if (typeof newConfiguration.tabSize !== 'undefined') {
346
			newOpts.tabSize = newConfiguration.tabSize;
347 348 349 350
		}
		this._model.updateOptions(newOpts);
	}

J
Johannes Rieken 已提交
351
	public setConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void {
352 353
		this._setIndentConfiguration(newConfiguration);

354 355 356 357
		if (!this._codeEditor) {
			return;
		}

358
		if (newConfiguration.cursorStyle) {
359
			let newCursorStyle = cursorStyleToString(newConfiguration.cursorStyle);
360 361 362 363
			this._codeEditor.updateOptions({
				cursorStyle: newCursorStyle
			});
		}
364 365

		if (typeof newConfiguration.lineNumbers !== 'undefined') {
366
			let lineNumbers: 'on' | 'off' | 'relative';
367 368
			switch (newConfiguration.lineNumbers) {
				case TextEditorLineNumbersStyle.On:
369
					lineNumbers = 'on';
370 371 372 373 374
					break;
				case TextEditorLineNumbersStyle.Relative:
					lineNumbers = 'relative';
					break;
				default:
375
					lineNumbers = 'off';
376
			}
377
			this._codeEditor.updateOptions({
378
				lineNumbers: lineNumbers
379 380
			});
		}
381 382
	}

383
	public setDecorations(key: string, ranges: editorCommon.IDecorationOptions[]): void {
384 385 386 387
		if (!this._codeEditor) {
			return;
		}
		this._codeEditor.setDecorations(key, ranges);
388 389 390 391 392 393 394 395 396 397 398
	}

	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);
399 400
	}

A
Alex Dima 已提交
401
	public revealRange(range: IRange, revealType: TextEditorRevealType): void {
402 403 404
		if (!this._codeEditor) {
			return;
		}
405 406
		switch (revealType) {
			case TextEditorRevealType.Default:
407
				this._codeEditor.revealRange(range, editorCommon.ScrollType.Smooth);
408 409
				break;
			case TextEditorRevealType.InCenter:
410
				this._codeEditor.revealRangeInCenter(range, editorCommon.ScrollType.Smooth);
411
				break;
412
			case TextEditorRevealType.InCenterIfOutsideViewport:
413
				this._codeEditor.revealRangeInCenterIfOutsideViewport(range, editorCommon.ScrollType.Smooth);
414 415
				break;
			case TextEditorRevealType.AtTop:
416
				this._codeEditor.revealRangeAtTop(range, editorCommon.ScrollType.Smooth);
417 418
				break;
			default:
419
				console.warn(`Unknown revealType: ${revealType}`);
420
				break;
421 422 423 424 425
		}
	}

	public isFocused(): boolean {
		if (this._codeEditor) {
A
Alex Dima 已提交
426
			return this._codeEditor.hasTextFocus();
427 428 429 430 431 432 433 434 435 436 437
		}
		return false;
	}

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

438
	public applyEdits(versionIdCheck: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): boolean {
439 440 441 442 443 444
		if (this._model.getVersionId() !== versionIdCheck) {
			// throw new Error('Model has changed in the meantime!');
			// model changed in the meantime
			return false;
		}

445 446 447 448
		if (!this._codeEditor) {
			// console.warn('applyEdits on invisible editor');
			return false;
		}
449

450
		if (opts.setEndOfLine === EndOfLine.CRLF) {
451
			this._model.setEOL(EndOfLineSequence.CRLF);
452
		} else if (opts.setEndOfLine === EndOfLine.LF) {
453
			this._model.setEOL(EndOfLineSequence.LF);
454 455
		}

456
		let transformedEdits = edits.map((edit): IIdentifiedSingleEditOperation => {
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
			return {
				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;
472
	}
473

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

476 477 478 479
		if (!this._codeEditor) {
			return false;
		}

480
		const snippetController = SnippetController2.get(this._codeEditor);
481

482 483
		// // cancel previous snippet mode
		// snippetController.leaveSnippet();
484 485

		// set selection, focus editor
486
		const selections = ranges.map(r => new Selection(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn));
487
		this._codeEditor.setSelections(selections);
488
		this._codeEditor.focus();
489

490
		// make modifications
491
		snippetController.insert(template, 0, 0, opts.undoStopBefore, opts.undoStopAfter);
492 493

		return true;
494
	}
495
}