editorStatus.ts 30.7 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  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 'vs/css!./media/editorstatus';
import nls = require('vs/nls');
10
import {TPromise} from 'vs/base/common/winjs.base';
A
Alex Dima 已提交
11
import { emmet as $, append } from 'vs/base/browser/dom';
E
Erich Gamma 已提交
12
import strings = require('vs/base/common/strings');
13
import paths = require('vs/base/common/paths');
E
Erich Gamma 已提交
14 15 16 17 18
import types = require('vs/base/common/types');
import uri from 'vs/base/common/uri';
import errors = require('vs/base/common/errors');
import {IStatusbarItem} from 'vs/workbench/browser/parts/statusbar/statusbar';
import {Action} from 'vs/base/common/actions';
19
import {IMode} from 'vs/editor/common/modes';
20
import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput';
B
Benjamin Pasero 已提交
21 22
import {IFileEditorInput, EncodingMode, IEncodingSupport, asFileEditorInput, getUntitledOrFileResource} from 'vs/workbench/common/editor';
import {IDisposable, combinedDispose} from 'vs/base/common/lifecycle';
23
import {IMessageService, Severity} from 'vs/platform/message/common/message';
A
Alex Dima 已提交
24
import {ICommonCodeEditor} from 'vs/editor/common/editorCommon';
25
import {OpenGlobalSettingsAction} from 'vs/workbench/browser/actions/openSettings';
26
import {ICodeEditor, IDiffEditor} from 'vs/editor/browser/editorBrowser';
I
isidor 已提交
27
import {TrimTrailingWhitespaceAction} from 'vs/editor/contrib/linesOperations/common/linesOperations';
E
Erich Gamma 已提交
28
import {EndOfLineSequence, ITokenizedModel, EditorType, IEditorSelection, ITextModel, IDiffEditorModel, IEditor} from 'vs/editor/common/editorCommon';
29
import {IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction} from 'vs/editor/contrib/indentation/common/indentation';
30
import {EventType, ResourceEvent, EditorEvent, TextEditorSelectionEvent} from 'vs/workbench/common/events';
E
Erich Gamma 已提交
31
import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor';
32
import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor';
E
Erich Gamma 已提交
33
import {IWorkbenchEditorService}  from 'vs/workbench/services/editor/common/editorService';
34
import {IQuickOpenService, IPickOpenEntry} from 'vs/workbench/services/quickopen/common/quickOpenService';
E
Erich Gamma 已提交
35 36
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {IEventService} from 'vs/platform/event/common/event';
37
import {IFilesConfiguration, SUPPORTED_ENCODINGS} from 'vs/platform/files/common/files';
E
Erich Gamma 已提交
38 39
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IModeService} from 'vs/editor/common/services/modeService';
A
Alex Dima 已提交
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
import {StyleMutator} from 'vs/base/browser/styleMutator';

function getCodeEditor(e: IBaseEditor): ICommonCodeEditor {
	if (e instanceof BaseTextEditor) {
		let editorWidget = e.getControl();
		if (editorWidget.getEditorType() === EditorType.IDiffEditor) {
			return (<IDiffEditor>editorWidget).getModifiedEditor();
		}
		if (editorWidget.getEditorType() === EditorType.ICodeEditor) {
			return (<ICodeEditor>editorWidget);
		}
		return null;
	}
	return null;
}
E
Erich Gamma 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

function getTextModel(editorWidget: IEditor): ITextModel {
	let textModel: ITextModel;

	// Support for diff
	let model = editorWidget.getModel();
	if (model && !!(<IDiffEditorModel>model).modified) {
		textModel = (<IDiffEditorModel>model).modified;
	}

	// Normal editor
	else {
		textModel = <ITextModel>model;
	}

	return textModel;
}

73
function asFileOrUntitledEditorInput(input: any): UntitledEditorInput | IFileEditorInput {
74 75 76 77 78 79 80
	if (input instanceof UntitledEditorInput) {
		return input;
	}

	return asFileEditorInput(input, true /* support diff editor */);
}

E
Erich Gamma 已提交
81 82 83 84 85
interface IEditorSelectionStatus {
	selections?: IEditorSelection[];
	charactersSelected?: number;
}

86
interface IStateChange {
I
isidor 已提交
87
	indentation: boolean;
88 89 90 91
	selectionStatus: boolean;
	mode: boolean;
	encoding: boolean;
	EOL: boolean;
E
Erich Gamma 已提交
92 93 94
	tabFocusMode: boolean;
}

95 96 97 98 99
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
100
	indentation?: string;
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
	tabFocusMode?: boolean;
}

class State {
	private _selectionStatus: string;
	public get selectionStatus(): string { return this._selectionStatus; }

	private _mode: string;
	public get mode(): string { return this._mode; }

	private _encoding: string;
	public get encoding(): string { return this._encoding; }

	private _EOL: string;
	public get EOL(): string { return this._EOL; }

I
isidor 已提交
117 118 119
	private _indentation: string;
	public get indentation(): string { return this._indentation; }

120 121 122 123 124 125 126 127 128 129 130
	private _tabFocusMode: boolean;
	public get tabFocusMode(): boolean { return this._tabFocusMode; }

	constructor() {
		this._selectionStatus = null;
		this._mode = null;
		this._encoding = null;
		this._EOL = null;
		this._tabFocusMode = false;
	}

131
	public update(update: StateDelta): IStateChange {
132 133 134 135 136
		let e = {
			selectionStatus: false,
			mode: false,
			encoding: false,
			EOL: false,
I
isidor 已提交
137 138
			tabFocusMode: false,
			indentation: false
139 140 141 142 143 144 145 146 147 148
		};
		let somethingChanged = false;

		if (typeof update.selectionStatus !== 'undefined') {
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
				somethingChanged = true;
				e.selectionStatus = true;
			}
		}
I
isidor 已提交
149
		if (typeof update.indentation !== 'undefined') {
150 151
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
I
isidor 已提交
152 153 154 155
				somethingChanged = true;
				e.indentation = true;
			}
		}
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
		if (typeof update.mode !== 'undefined') {
			if (this._mode !== update.mode) {
				this._mode = update.mode;
				somethingChanged = true;
				e.mode = true;
			}
		}
		if (typeof update.encoding !== 'undefined') {
			if (this._encoding !== update.encoding) {
				this._encoding = update.encoding;
				somethingChanged = true;
				e.encoding = true;
			}
		}
		if (typeof update.EOL !== 'undefined') {
			if (this._EOL !== update.EOL) {
				this._EOL = update.EOL;
				somethingChanged = true;
				e.EOL = true;
			}
		}
		if (typeof update.tabFocusMode !== 'undefined') {
			if (this._tabFocusMode !== update.tabFocusMode) {
				this._tabFocusMode = update.tabFocusMode;
				somethingChanged = true;
				e.tabFocusMode = true;
			}
		}

		if (somethingChanged) {
			return e;
		}
		return null;
	}
}

192 193 194 195 196 197
const nlsSingleSelectionRange = nls.localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)");
const nlsSingleSelection = nls.localize('singleSelection', "Ln {0}, Col {1}");
const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selections ({1} characters selected)");
const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
198
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab moves focus");
E
Erich Gamma 已提交
199

200
function show(el: HTMLElement): void {
A
Alex Dima 已提交
201 202
	StyleMutator.setDisplay(el, '');
}
203
function hide(el: HTMLElement): void {
A
Alex Dima 已提交
204 205 206
	StyleMutator.setDisplay(el, 'none');
}

207
export class EditorStatus implements IStatusbarItem {
E
Erich Gamma 已提交
208

209
	private state: State;
210 211
	private element: HTMLElement;
	private tabFocusModeElement: HTMLElement;
I
isidor 已提交
212
	private indentationElement: HTMLElement;
213 214 215 216
	private selectionElement: HTMLElement;
	private encodingElement: HTMLElement;
	private eolElement: HTMLElement;
	private modeElement: HTMLElement;
E
Erich Gamma 已提交
217 218
	private toDispose: IDisposable[];

219 220 221 222
	constructor(
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IInstantiationService private instantiationService: IInstantiationService,
223
		@IEventService private eventService: IEventService,
224 225
		@IModeService private modeService: IModeService,
		@IConfigurationService private configurationService: IConfigurationService
226 227
	) {
		this.toDispose = [];
228
		this.state = new State();
E
Erich Gamma 已提交
229 230
	}

231 232 233 234 235 236 237
	public render(container: HTMLElement): IDisposable {
		this.element = append(container, $('.editor-statusbar-item'));

		this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode'));
		this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode");
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;
238
		hide(this.tabFocusModeElement);
239 240 241 242

		this.selectionElement = append(this.element, $('a.editor-status-selection'));
		this.selectionElement.title = nls.localize('gotoLine', "Go to Line");
		this.selectionElement.onclick = () => this.onSelectionClick();
243
		hide(this.selectionElement);
244

245 246 247 248 249
		this.indentationElement = append(this.element, $('a.editor-status-indentation'));
		this.indentationElement.title = nls.localize('indentation', "Indentation");
		this.indentationElement.onclick = () => this.onIndentationClick();
		hide(this.indentationElement);

250 251 252
		this.encodingElement = append(this.element, $('a.editor-status-encoding'));
		this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding");
		this.encodingElement.onclick = () => this.onEncodingClick();
253
		hide(this.encodingElement);
254 255 256 257

		this.eolElement = append(this.element, $('a.editor-status-eol'));
		this.eolElement.title = nls.localize('selectEOL', "Select End of Line Sequence");
		this.eolElement.onclick = () => this.onEOLClick();
258
		hide(this.eolElement);
259 260 261 262

		this.modeElement = append(this.element, $('a.editor-status-mode'));
		this.modeElement.title = nls.localize('selectLanguageMode', "Select Language Mode");
		this.modeElement.onclick = () => this.onModeClick();
263
		hide(this.modeElement);
264 265 266 267 268 269 270

		this.toDispose.push(
			this.eventService.addListener2(EventType.EDITOR_INPUT_CHANGED, (e: EditorEvent) => this.onEditorInputChange(e.editor)),
			this.eventService.addListener2(EventType.RESOURCE_ENCODING_CHANGED, (e: ResourceEvent) => this.onResourceEncodingChange(e.resource)),
			this.eventService.addListener2(EventType.TEXT_EDITOR_SELECTION_CHANGED, (e: TextEditorSelectionEvent) => this.onSelectionChange(e.editor)),
			this.eventService.addListener2(EventType.TEXT_EDITOR_MODE_CHANGED, (e: EditorEvent) => this.onModeChange(e.editor)),
			this.eventService.addListener2(EventType.TEXT_EDITOR_CONTENT_CHANGED, (e: EditorEvent) => this.onEOLChange(e.editor)),
I
isidor 已提交
271
			this.eventService.addListener2(EventType.TEXT_EDITOR_CONFIGURATION_CHANGED, (e: EditorEvent) => this.onTabFocusModeChange(e.editor)),
I
isidor 已提交
272
			this.eventService.addListener2(EventType.TEXT_EDITOR_CONTENT_OPTIONS_CHANGED, (e: EditorEvent) => this.onIndentationChange(e.editor))
273
		);
E
Erich Gamma 已提交
274

275 276 277
		return combinedDispose(...this.toDispose);
	}

278 279 280 281 282
	private updateState(update: StateDelta): void {
		let changed = this.state.update(update);
		if (!changed) {
			// Nothing really changed
			return;
E
Erich Gamma 已提交
283 284
		}

285 286 287 288 289 290
		if (changed.tabFocusMode) {
			if (this.state.tabFocusMode && this.state.tabFocusMode === true) {
				show(this.tabFocusModeElement);
			} else {
				hide(this.tabFocusModeElement);
			}
E
Erich Gamma 已提交
291 292
		}

I
isidor 已提交
293 294 295 296 297 298 299 300 301
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

302 303 304 305 306 307 308
		if (changed.selectionStatus) {
			if (this.state.selectionStatus) {
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
309 310
		}

311 312 313 314 315 316 317
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
318 319
		}

320 321 322 323 324 325 326
		if (changed.EOL) {
			if (this.state.EOL) {
				this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF;
				show(this.eolElement);
			} else {
				hide(this.eolElement);
			}
E
Erich Gamma 已提交
327 328
		}

329 330 331 332 333 334 335 336
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
E
Erich Gamma 已提交
337 338
	}

339
	private getSelectionLabel(info: IEditorSelectionStatus): string {
E
Erich Gamma 已提交
340 341 342 343 344 345
		if (!info || !info.selections) {
			return null;
		}

		if (info.selections.length === 1) {
			if (info.charactersSelected) {
346
				return strings.format(nlsSingleSelectionRange, info.selections[0].positionLineNumber, info.selections[0].positionColumn, info.charactersSelected);
E
Erich Gamma 已提交
347
			} else {
348
				return strings.format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
349 350 351
			}
		} else {
			if (info.charactersSelected) {
352
				return strings.format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected);
E
Erich Gamma 已提交
353
			} else {
354
				return strings.format(nlsMultiSelection, info.selections.length);
E
Erich Gamma 已提交
355 356 357 358
			}
		}
	}

359 360 361 362 363 364 365
	private onModeClick(): void {
		let action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);

		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

I
isidor 已提交
366 367 368 369 370 371
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
		let action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);

		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

	private onEncodingClick(): void {
		let action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);

		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

	private onTabFocusModeClick(): void {
		let activeEditor = this.editorService.getActiveEditor();
		if (activeEditor instanceof BaseTextEditor && isCodeEditorWithTabFocusMode(activeEditor)) {
			(<ICodeEditor>activeEditor.getControl()).updateOptions({ tabFocusMode: false });
		}
	}

397
	private onEditorInputChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
398 399 400 401 402
		this.onSelectionChange(e);
		this.onModeChange(e);
		this.onEOLChange(e);
		this.onEncodingChange(e);
		this.onTabFocusModeChange(e);
I
isidor 已提交
403
		this.onIndentationChange(e);
E
Erich Gamma 已提交
404 405
	}

406
	private onModeChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
407 408 409 410
		if (e && !this.isActiveEditor(e)) {
			return;
		}

411
		let info: StateDelta = { mode: null };
E
Erich Gamma 已提交
412 413 414 415 416 417 418 419 420 421

		// We only support text based editors
		if (e instanceof BaseTextEditor) {
			let editorWidget = e.getControl();
			let textModel = getTextModel(editorWidget);
			if (textModel) {
				// Compute mode
				if (!!(<ITokenizedModel>textModel).getMode) {
					let mode = (<ITokenizedModel>textModel).getMode();
					if (mode) {
422
						info = { mode: this.modeService.getLanguageName(mode.getId()) };
E
Erich Gamma 已提交
423 424 425 426 427 428 429 430
					}
				}
			}
		}

		this.updateState(info);
	}

I
isidor 已提交
431
	private onIndentationChange(e: IBaseEditor): void {
432 433 434 435 436
		if (e && !this.isActiveEditor(e)) {
			return;
		}

		const update: StateDelta = { indentation: null };
437

I
isidor 已提交
438
		if (e instanceof BaseTextEditor) {
439 440 441 442 443
			let editorWidget = e.getControl();
			if (editorWidget) {
				if (editorWidget.getEditorType() === EditorType.IDiffEditor) {
					editorWidget = (<IDiffEditor>editorWidget).getModifiedEditor();
				}
444

I
isidor 已提交
445
				const model = (<ICommonCodeEditor>editorWidget).getModel();
446
				if (model) {
I
isidor 已提交
447 448
					const modelOpts = model.getOptions();
					update.indentation = (
449
						modelOpts.insertSpaces
450 451
							? nls.localize('spacesSize', "Spaces: {0}", modelOpts.tabSize)
							: nls.localize('tabSize', "Tab Size: {0}", modelOpts.tabSize)
452
					);
453
				}
454
			}
I
isidor 已提交
455
		}
456

I
isidor 已提交
457 458 459
		this.updateState(update);
	}

460
	private onSelectionChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
		if (e && !this.isActiveEditor(e)) {
			return;
		}

		let info: IEditorSelectionStatus = {};

		// We only support text based editors
		if (e instanceof BaseTextEditor) {
			let editorWidget = e.getControl();

			// Compute selection(s)
			info.selections = editorWidget.getSelections() || [];

			// Compute selection length
			info.charactersSelected = 0;
			let textModel = getTextModel(editorWidget);
			if (textModel) {
				info.selections.forEach((selection) => {
					info.charactersSelected += textModel.getValueLengthInRange(selection);
				});
			}

			// Compute the visible column for one selection. This will properly handle tabs and their configured widths
			if (info.selections.length === 1) {
				let visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition());

				let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor
				selectionClone.positionColumn = visibleColumn;

				info.selections[0] = selectionClone;
			}
		}

494
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
495 496
	}

497
	private onEOLChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
498 499 500 501
		if (e && !this.isActiveEditor(e)) {
			return;
		}

A
Alex Dima 已提交
502
		let info: StateDelta = { EOL: null };
A
Alex Dima 已提交
503 504 505

		let codeEditor = getCodeEditor(e);
		if (codeEditor && !codeEditor.getConfiguration().readOnly) {
A
Alex Dima 已提交
506
			let codeEditorModel = codeEditor.getModel();
I
isidor 已提交
507 508 509
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
510 511 512 513 514
		}

		this.updateState(info);
	}

515
	private onEncodingChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
516 517 518 519
		if (e && !this.isActiveEditor(e)) {
			return;
		}

520
		let info: StateDelta = { encoding: null };
E
Erich Gamma 已提交
521 522 523

		// We only support text based editors
		if (e instanceof BaseTextEditor) {
524
			let encodingSupport: IEncodingSupport = <any>asFileOrUntitledEditorInput(e.input);
E
Erich Gamma 已提交
525 526
			if (encodingSupport && types.isFunction(encodingSupport.getEncoding)) {
				let rawEncoding = encodingSupport.getEncoding();
527
				let encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
528 529 530 531 532 533 534 535 536 537 538
				if (encodingInfo) {
					info.encoding = encodingInfo.labelShort; // if we have a label, take it from there
				} else {
					info.encoding = rawEncoding; // otherwise use it raw
				}
			}
		}

		this.updateState(info);
	}

539 540 541 542 543
	private onResourceEncodingChange(resource: uri): void {
		let activeEditor = this.editorService.getActiveEditor();
		if (activeEditor) {
			let activeResource = getUntitledOrFileResource(activeEditor.input, true);
			if (activeResource && activeResource.toString() === resource.toString()) {
544
				return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
545 546
			}
		}
E
Erich Gamma 已提交
547 548
	}

549
	private onTabFocusModeChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
550 551 552 553
		if (e && !this.isActiveEditor(e)) {
			return;
		}

554
		let info: StateDelta = { tabFocusMode: false };
E
Erich Gamma 已提交
555 556 557 558 559 560 561 562 563

		// We only support text based editors
		if (e instanceof BaseTextEditor && isCodeEditorWithTabFocusMode(e)) {
			info = { tabFocusMode: true };
		}

		this.updateState(info);
	}

564
	private isActiveEditor(e: IBaseEditor): boolean {
565
		let activeEditor = this.editorService.getActiveEditor();
E
Erich Gamma 已提交
566 567 568 569 570 571 572

		return activeEditor && e && activeEditor === e;
	}
}

function isCodeEditorWithTabFocusMode(e: BaseTextEditor): boolean {
	let editorWidget = e.getControl();
573 574 575
	if (editorWidget.getEditorType() === EditorType.IDiffEditor) {
		editorWidget = (<IDiffEditor>editorWidget).getModifiedEditor();
	}
576 577 578
	if (editorWidget.getEditorType() !== EditorType.ICodeEditor) {
		return false;
	}
579

580 581
	let editorConfig = (<ICodeEditor>editorWidget).getConfiguration();
	return editorConfig.tabFocusMode && !editorConfig.readOnly;
E
Erich Gamma 已提交
582 583 584 585
}

function isWritableCodeEditor(e: BaseTextEditor): boolean {
	let editorWidget = e.getControl();
586 587 588 589
	if (editorWidget.getEditorType() === EditorType.IDiffEditor) {
		editorWidget = (<IDiffEditor>editorWidget).getModifiedEditor();
	}

E
Erich Gamma 已提交
590 591 592 593 594 595 596 597 598 599 600 601 602 603
	return (editorWidget.getEditorType() === EditorType.ICodeEditor &&
		!(<ICodeEditor>editorWidget).getConfiguration().readOnly);
}

export class ChangeModeAction extends Action {

	public static ID = 'workbench.action.editor.changeLanguageMode';
	public static LABEL = nls.localize('changeMode', "Change Language Mode");

	constructor(
		actionId: string,
		actionLabel: string,
		@IModeService private modeService: IModeService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
604 605
		@IMessageService private messageService: IMessageService,
		@IInstantiationService private instantiationService: IInstantiationService,
E
Erich Gamma 已提交
606 607 608 609 610
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
611
	public run(): TPromise<any> {
612
		let languages = this.modeService.getRegisteredLanguageNames();
E
Erich Gamma 已提交
613 614 615 616 617 618 619
		let activeEditor = this.editorService.getActiveEditor();
		if (!(activeEditor instanceof BaseTextEditor)) {
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

		let editorWidget = (<BaseTextEditor>activeEditor).getControl();
		let textModel = getTextModel(editorWidget);
620
		let fileinput = asFileEditorInput(activeEditor.input, true);
E
Erich Gamma 已提交
621 622 623 624 625 626

		// Compute mode
		let currentModeId: string;
		if (!!(<ITokenizedModel>textModel).getMode) {
			let mode = (<ITokenizedModel>textModel).getMode();
			if (mode) {
627
				currentModeId = this.modeService.getLanguageName(mode.getId());
E
Erich Gamma 已提交
628 629 630 631 632 633
			}
		}

		// All languages are valid picks
		let picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
			return {
634 635
				label: lang,
				description: currentModeId === lang ? nls.localize('configuredLanguage', "Configured Language") : void 0
E
Erich Gamma 已提交
636 637
			};
		});
638
		picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages") };
E
Erich Gamma 已提交
639

640
		// Offer action to configure via settings
B
wording  
Benjamin Pasero 已提交
641
		let configureLabel = nls.localize('configureAssociations', "Configure File Associations...");
642 643 644 645
		if (fileinput) {
			const resource = fileinput.getResource();
			const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
			if (ext) {
B
wording  
Benjamin Pasero 已提交
646
				configureLabel = nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext);
647 648 649
			}
		}

650
		let configureModeAssociations: IPickOpenEntry = {
651
			label: configureLabel
652 653
		};
		picks.unshift(configureModeAssociations);
E
Erich Gamma 已提交
654

655 656 657 658 659
		// Offer to "Auto Detect"
		let autoDetectMode: IPickOpenEntry = {
			label: nls.localize('autoDetect', "Auto Detect")
		};
		picks.unshift(autoDetectMode);
660

661
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode") }).then((language) => {
E
Erich Gamma 已提交
662 663 664 665
			if (language) {
				activeEditor = this.editorService.getActiveEditor();
				if (activeEditor instanceof BaseTextEditor) {
					let editorWidget = activeEditor.getControl();
666 667
					let models: ITextModel[] = [];

E
Erich Gamma 已提交
668
					let textModel = getTextModel(editorWidget);
669 670 671 672 673 674 675 676 677 678 679
					models.push(textModel);

					// Support for original side of diff
					let model = editorWidget.getModel();
					if (model && !!(<IDiffEditorModel>model).original) {
						models.push((<IDiffEditorModel>model).original);
					}

					// Find mode
					let mode: TPromise<IMode>;
					if (language === autoDetectMode) {
680
						mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(getUntitledOrFileResource(activeEditor.input, true).fsPath, textModel.getLineContent(1));
681 682 683 684
					} else if (language === configureModeAssociations) {
						const action = this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL);
						action.run().done(() => action.dispose(), errors.onUnexpectedError);

685
						this.messageService.show(Severity.Info, nls.localize('persistFileAssociations', "You can configure filename to language associations in the **files.associations** section. The changes may need a restart to take effect on already opened files."));
686 687 688
					} else {
						mode = this.modeService.getOrCreateModeByLanguageName(language.label);
					}
E
Erich Gamma 已提交
689 690

					// Change mode
691 692 693
					models.forEach((textModel) => {
						if (!!(<ITokenizedModel>textModel).getMode) {
							(<ITokenizedModel>textModel).setMode(mode);
E
Erich Gamma 已提交
694
						}
695
					});
E
Erich Gamma 已提交
696 697 698 699 700 701 702 703 704 705
				}
			}
		});
	}
}

export interface IChangeEOLEntry extends IPickOpenEntry {
	eol: EndOfLineSequence;
}

I
isidor 已提交
706
class ChangeIndentationAction extends Action {
I
isidor 已提交
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727

	public static ID = 'workbench.action.editor.changeIndentation';
	public static LABEL = nls.localize('changeIndentation', "Change Indentation");

	constructor(
		actionId: string,
		actionLabel: string,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

	public run(): TPromise<any> {
		const activeEditor = this.editorService.getActiveEditor();
		if (!(activeEditor instanceof BaseTextEditor)) {
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
		if (!isWritableCodeEditor(<BaseTextEditor>activeEditor)) {
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}
728

729
		const control = <ICommonCodeEditor>activeEditor.getControl();
I
isidor 已提交
730 731
		const picks = [control.getAction(IndentUsingSpaces.ID), control.getAction(IndentUsingTabs.ID), control.getAction(DetectIndentation.ID),
			control.getAction(IndentationToSpacesAction.ID), control.getAction(IndentationToTabsAction.ID), control.getAction(TrimTrailingWhitespaceAction.ID)];
B
Benjamin Pasero 已提交
732
		(<IPickOpenEntry>picks[0]).separator = { label: nls.localize('indentView', "change view") };
733
		(<IPickOpenEntry>picks[3]).separator = { label: nls.localize('indentConvert', "convert file"), border: true };
I
isidor 已提交
734

735
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action") }).then(action => action && action.run());
I
isidor 已提交
736 737 738
	}
}

E
Erich Gamma 已提交
739 740 741 742 743 744 745 746 747 748 749 750 751 752
export class ChangeEOLAction extends Action {

	public static ID = 'workbench.action.editor.changeEOL';
	public static LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");

	constructor(
		actionId: string,
		actionLabel: string,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
753
	public run(): TPromise<any> {
E
Erich Gamma 已提交
754 755 756 757 758 759 760 761 762 763 764 765 766 767

		let activeEditor = this.editorService.getActiveEditor();
		if (!(activeEditor instanceof BaseTextEditor)) {
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

		if (!isWritableCodeEditor(<BaseTextEditor>activeEditor)) {
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}

		let editorWidget = (<BaseTextEditor>activeEditor).getControl();
		let textModel = getTextModel(editorWidget);

		let EOLOptions: IChangeEOLEntry[] = [
768 769
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
		];

		let selectedIndex = (textModel.getEOL() === '\n') ? 0 : 1;

		return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then((eol) => {
			if (eol) {
				activeEditor = this.editorService.getActiveEditor();
				if (activeEditor instanceof BaseTextEditor && isWritableCodeEditor(activeEditor)) {
					let editorWidget = activeEditor.getControl();
					let textModel = getTextModel(editorWidget);
					textModel.setEOL(eol.eol);
				}
			}
		});
	}
}

export class ChangeEncodingAction extends Action {

	public static ID = 'workbench.action.editor.changeEncoding';
	public static LABEL = nls.localize('changeEncoding', "Change File Encoding");

	constructor(
		actionId: string,
		actionLabel: string,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IConfigurationService private configurationService: IConfigurationService
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
802
	public run(): TPromise<any> {
E
Erich Gamma 已提交
803 804 805 806 807
		let activeEditor = this.editorService.getActiveEditor();
		if (!(activeEditor instanceof BaseTextEditor) || !activeEditor.input) {
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

808
		let encodingSupport: IEncodingSupport = <any>asFileOrUntitledEditorInput(activeEditor.input);
E
Erich Gamma 已提交
809 810 811 812 813 814 815 816
		if (!types.areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
			return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
		}

		let pickActionPromise: TPromise<IPickOpenEntry>;
		let saveWithEncodingPick: IPickOpenEntry = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
		let reopenWithEncodingPick: IPickOpenEntry = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };

817
		if (encodingSupport instanceof UntitledEditorInput) {
A
Alex Dima 已提交
818
			pickActionPromise = TPromise.as(saveWithEncodingPick);
E
Erich Gamma 已提交
819
		} else if (!isWritableCodeEditor(<BaseTextEditor>activeEditor)) {
A
Alex Dima 已提交
820
			pickActionPromise = TPromise.as(reopenWithEncodingPick);
E
Erich Gamma 已提交
821 822 823 824 825 826 827 828 829
		} else {
			pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action") });
		}

		return pickActionPromise.then((action) => {
			if (!action) {
				return;
			}

830
			return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).then(() => {
E
Erich Gamma 已提交
831 832 833
				let isReopenWithEncoding = (action === reopenWithEncodingPick);

				return this.configurationService.loadConfiguration().then((configuration: IFilesConfiguration) => {
834 835 836
					let configuredEncoding = configuration && configuration.files && configuration.files.encoding;
					let directMatchIndex: number;
					let aliasMatchIndex: number;
E
Erich Gamma 已提交
837 838

					// All encodings are valid picks
839
					let picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS)
E
Erich Gamma 已提交
840
						.sort((k1, k2) => {
841
							if (k1 === configuredEncoding) {
E
Erich Gamma 已提交
842
								return -1;
843
							} else if (k2 === configuredEncoding) {
E
Erich Gamma 已提交
844 845 846
								return 1;
							}

847
							return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order;
E
Erich Gamma 已提交
848
						})
B
wip  
Benjamin Pasero 已提交
849
						.filter(k => {
850
							return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode
B
wip  
Benjamin Pasero 已提交
851
						})
E
Erich Gamma 已提交
852
						.map((key, index) => {
853 854 855 856
							if (key === encodingSupport.getEncoding()) {
								directMatchIndex = index;
							} else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) {
								aliasMatchIndex = index;
E
Erich Gamma 已提交
857 858
							}

B
Benjamin Pasero 已提交
859
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong };
E
Erich Gamma 已提交
860 861 862 863
						});

					return this.quickOpenService.pick(picks, {
						placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
864
						autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
E
Erich Gamma 已提交
865 866 867
					}).then((encoding) => {
						if (encoding) {
							activeEditor = this.editorService.getActiveEditor();
868
							encodingSupport = <any>asFileOrUntitledEditorInput(activeEditor.input);
E
Erich Gamma 已提交
869
							if (encodingSupport && types.areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding) && encodingSupport.getEncoding() !== encoding.id) {
870
								encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
E
Erich Gamma 已提交
871 872 873 874 875 876 877 878
							}
						}
					});
				});
			});
		});
	}
}