editorStatus.ts 40.9 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');
J
Johannes Rieken 已提交
10 11
import { TPromise } from 'vs/base/common/winjs.base';
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } 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
import types = require('vs/base/common/types');
import uri from 'vs/base/common/uri';
import errors = require('vs/base/common/errors');
J
Johannes Rieken 已提交
17 18
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Action } from 'vs/base/common/actions';
19
import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform';
J
Johannes Rieken 已提交
20 21
import { IMode } from 'vs/editor/common/modes';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
22
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
J
Johannes Rieken 已提交
23 24 25
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
26
import { IEditorAction, ICommonCodeEditor, EndOfLineSequence, IModel } from 'vs/editor/common/editorCommon';
27
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
J
Johannes Rieken 已提交
28
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/common/linesOperations';
29
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/common/indentation';
J
Johannes Rieken 已提交
30 31
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
32
import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/editor';
J
Johannes Rieken 已提交
33
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Johannes Rieken 已提交
34
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
J
Johannes Rieken 已提交
35
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
36
import { SUPPORTED_ENCODINGS, IFileService } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
37 38 39 40 41 42
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Selection } from 'vs/editor/common/core/selection';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
43
import { ICommandService } from 'vs/platform/commands/common/commands';
44
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
45
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
S
Sandeep Somavarapu 已提交
46
import { getCodeEditor as getEditorWidget } from 'vs/editor/common/services/codeEditorService';
47
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
B
Benjamin Pasero 已提交
48
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
49

B
Benjamin Pasero 已提交
50 51 52 53
// TODO@Sandeep layer breaker
// tslint:disable-next-line:import-patterns
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';

54 55 56 57 58
function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport {
	if (input instanceof SideBySideEditorInput) {
		input = input.master;
	}

59 60 61 62
	if (input instanceof UntitledEditorInput) {
		return input;
	}

63 64 65 66 67 68
	let encodingSupport = input as IFileEditorInput;
	if (types.areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
		return encodingSupport;
	}

	return null;
69 70
}

E
Erich Gamma 已提交
71
interface IEditorSelectionStatus {
72
	selections?: Selection[];
E
Erich Gamma 已提交
73 74 75
	charactersSelected?: number;
}

76
class StateChange {
A
Alex Dima 已提交
77
	_stateChangeBrand: void;
78

I
isidor 已提交
79
	indentation: boolean;
80 81 82 83
	selectionStatus: boolean;
	mode: boolean;
	encoding: boolean;
	EOL: boolean;
E
Erich Gamma 已提交
84
	tabFocusMode: boolean;
85
	screenReaderMode: boolean;
86
	metadata: boolean;
87 88 89 90 91 92 93 94

	constructor() {
		this.indentation = false;
		this.selectionStatus = false;
		this.mode = false;
		this.encoding = false;
		this.EOL = false;
		this.tabFocusMode = false;
95
		this.screenReaderMode = false;
96
		this.metadata = false;
97 98
	}

99
	public combine(other: StateChange) {
100 101 102 103 104 105
		this.indentation = this.indentation || other.indentation;
		this.selectionStatus = this.selectionStatus || other.selectionStatus;
		this.mode = this.mode || other.mode;
		this.encoding = this.encoding || other.encoding;
		this.EOL = this.EOL || other.EOL;
		this.tabFocusMode = this.tabFocusMode || other.tabFocusMode;
106
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
107
		this.metadata = this.metadata || other.metadata;
108
	}
E
Erich Gamma 已提交
109 110
}

111 112 113 114 115
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
116
	indentation?: string;
117
	tabFocusMode?: boolean;
118
	screenReaderMode?: boolean;
119
	metadata?: string;
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
}

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 已提交
135 136 137
	private _indentation: string;
	public get indentation(): string { return this._indentation; }

138 139 140
	private _tabFocusMode: boolean;
	public get tabFocusMode(): boolean { return this._tabFocusMode; }

141 142 143
	private _screenReaderMode: boolean;
	public get screenReaderMode(): boolean { return this._screenReaderMode; }

144 145 146
	private _metadata: string;
	public get metadata(): string { return this._metadata; }

147 148 149 150 151 152
	constructor() {
		this._selectionStatus = null;
		this._mode = null;
		this._encoding = null;
		this._EOL = null;
		this._tabFocusMode = false;
153
		this._screenReaderMode = false;
154
		this._metadata = null;
155 156
	}

157
	public update(update: StateDelta): StateChange {
B
Benjamin Pasero 已提交
158
		const e = new StateChange();
159 160 161 162 163 164 165 166 167
		let somethingChanged = false;

		if (typeof update.selectionStatus !== 'undefined') {
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
				somethingChanged = true;
				e.selectionStatus = true;
			}
		}
I
isidor 已提交
168
		if (typeof update.indentation !== 'undefined') {
169 170
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
I
isidor 已提交
171 172 173 174
				somethingChanged = true;
				e.indentation = true;
			}
		}
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
		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;
			}
		}
203 204 205 206 207 208 209
		if (typeof update.screenReaderMode !== 'undefined') {
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
				somethingChanged = true;
				e.screenReaderMode = true;
			}
		}
210 211 212 213 214 215 216
		if (typeof update.metadata !== 'undefined') {
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
				somethingChanged = true;
				e.metadata = true;
			}
		}
217 218 219 220 221 222 223 224

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

225 226 227 228 229 230
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");
J
Jens Hausdorf 已提交
231 232
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Detected");
A
Alex Dima 已提交
233
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
E
Erich Gamma 已提交
234

A
Alex Dima 已提交
235 236 237 238 239
function _setDisplay(el: HTMLElement, desiredValue: string): void {
	if (el.style.display !== desiredValue) {
		el.style.display = desiredValue;
	}
}
240
function show(el: HTMLElement): void {
A
Alex Dima 已提交
241
	_setDisplay(el, '');
A
Alex Dima 已提交
242
}
243
function hide(el: HTMLElement): void {
A
Alex Dima 已提交
244
	_setDisplay(el, 'none');
A
Alex Dima 已提交
245 246
}

247
export class EditorStatus implements IStatusbarItem {
E
Erich Gamma 已提交
248

249
	private state: State;
250 251
	private element: HTMLElement;
	private tabFocusModeElement: HTMLElement;
252
	private screenRedearModeElement: HTMLElement;
I
isidor 已提交
253
	private indentationElement: HTMLElement;
254 255 256 257
	private selectionElement: HTMLElement;
	private encodingElement: HTMLElement;
	private eolElement: HTMLElement;
	private modeElement: HTMLElement;
258
	private metadataElement: HTMLElement;
E
Erich Gamma 已提交
259
	private toDispose: IDisposable[];
260
	private activeEditorListeners: IDisposable[];
261 262
	private delayedRender: IDisposable;
	private toRender: StateChange;
E
Erich Gamma 已提交
263

264 265
	constructor(
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
266
		@IEditorGroupService private editorGroupService: IEditorGroupService,
267 268
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IInstantiationService private instantiationService: IInstantiationService,
269
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
270
		@IModeService private modeService: IModeService,
271
		@ITextFileService private textFileService: ITextFileService
272 273
	) {
		this.toDispose = [];
274
		this.activeEditorListeners = [];
275
		this.state = new State();
E
Erich Gamma 已提交
276 277
	}

278 279 280
	public render(container: HTMLElement): IDisposable {
		this.element = append(container, $('.editor-statusbar-item'));

281
		this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
282 283 284
		this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode");
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;
285
		hide(this.tabFocusModeElement);
286

287 288
		this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info'));
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
289
		this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle;
290 291
		hide(this.screenRedearModeElement);

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

297 298 299 300 301
		this.indentationElement = append(this.element, $('a.editor-status-indentation'));
		this.indentationElement.title = nls.localize('indentation', "Indentation");
		this.indentationElement.onclick = () => this.onIndentationClick();
		hide(this.indentationElement);

302 303 304
		this.encodingElement = append(this.element, $('a.editor-status-encoding'));
		this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding");
		this.encodingElement.onclick = () => this.onEncodingClick();
305
		hide(this.encodingElement);
306 307 308 309

		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();
310
		hide(this.eolElement);
311 312 313 314

		this.modeElement = append(this.element, $('a.editor-status-mode'));
		this.modeElement.title = nls.localize('selectLanguageMode', "Select Language Mode");
		this.modeElement.onclick = () => this.onModeClick();
315
		hide(this.modeElement);
316

317 318 319 320
		this.metadataElement = append(this.element, $('span.editor-status-metadata'));
		this.metadataElement.title = nls.localize('fileInfo', "File Information");
		hide(this.metadataElement);

321 322 323
		this.delayedRender = null;
		this.toRender = null;

324
		this.toDispose.push(
325 326 327 328 329 330 331 332
			{
				dispose: () => {
					if (this.delayedRender) {
						this.delayedRender.dispose();
						this.delayedRender = null;
					}
				}
			},
333
			this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()),
334 335
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
336
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
337
		);
E
Erich Gamma 已提交
338

J
Joao Moreno 已提交
339
		return combinedDisposable(this.toDispose);
340 341
	}

342
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
343
		const changed = this.state.update(update);
344 345 346
		if (!changed) {
			// Nothing really changed
			return;
E
Erich Gamma 已提交
347 348
		}

349 350 351 352
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
353
				const toRender = this.toRender;
354 355 356 357 358 359 360 361
				this.toRender = null;
				this._renderNow(toRender);
			});
		} else {
			this.toRender.combine(changed);
		}
	}

362
	private _renderNow(changed: StateChange): void {
363 364 365 366 367 368
		if (changed.tabFocusMode) {
			if (this.state.tabFocusMode && this.state.tabFocusMode === true) {
				show(this.tabFocusModeElement);
			} else {
				hide(this.tabFocusModeElement);
			}
E
Erich Gamma 已提交
369 370
		}

371 372 373 374 375 376 377 378
		if (changed.screenReaderMode) {
			if (this.state.screenReaderMode && this.state.screenReaderMode === true) {
				show(this.screenRedearModeElement);
			} else {
				hide(this.screenRedearModeElement);
			}
		}

I
isidor 已提交
379 380 381 382 383 384 385 386 387
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

388
		if (changed.selectionStatus) {
389
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
390 391 392 393 394
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
395 396
		}

397 398 399 400 401 402 403
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
404 405
		}

406 407 408 409 410 411 412
		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 已提交
413 414
		}

415 416 417 418 419 420 421 422
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
423 424 425 426 427 428 429 430 431

		if (changed.metadata) {
			if (this.state.metadata) {
				this.metadataElement.textContent = this.state.metadata;
				show(this.metadataElement);
			} else {
				hide(this.metadataElement);
			}
		}
E
Erich Gamma 已提交
432 433
	}

434
	private getSelectionLabel(info: IEditorSelectionStatus): string {
E
Erich Gamma 已提交
435 436 437 438 439 440
		if (!info || !info.selections) {
			return null;
		}

		if (info.selections.length === 1) {
			if (info.charactersSelected) {
441
				return strings.format(nlsSingleSelectionRange, info.selections[0].positionLineNumber, info.selections[0].positionColumn, info.charactersSelected);
E
Erich Gamma 已提交
442
			}
B
Benjamin Pasero 已提交
443 444

			return strings.format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
445
		}
B
Benjamin Pasero 已提交
446 447 448 449 450 451 452 453 454 455

		if (info.charactersSelected) {
			return strings.format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected);
		}

		if (info.selections.length > 0) {
			return strings.format(nlsMultiSelection, info.selections.length);
		}

		return null;
E
Erich Gamma 已提交
456 457
	}

458
	private onModeClick(): void {
B
Benjamin Pasero 已提交
459
		const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
460 461 462 463 464

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

I
isidor 已提交
465 466 467 468 469 470
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

471 472 473 474 475
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
B
Benjamin Pasero 已提交
476
		const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
477 478 479 480 481 482

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

	private onEncodingClick(): void {
B
Benjamin Pasero 已提交
483
		const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
484 485 486 487 488 489

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

	private onTabFocusModeClick(): void {
490
		TabFocus.setTabFocusMode(false);
491 492
	}

493
	private onEditorsChanged(): void {
494
		const activeEditor = this.editorService.getActiveEditor();
A
Alex Dima 已提交
495
		const control = getEditorWidget(activeEditor);
496 497

		// Update all states
498
		this.onScreenReaderModeChange(control);
499 500 501
		this.onSelectionChange(control);
		this.onModeChange(control);
		this.onEOLChange(control);
B
Benjamin Pasero 已提交
502
		this.onEncodingChange(activeEditor);
503
		this.onIndentationChange(control);
504
		this.onMetadataChange(activeEditor);
E
Erich Gamma 已提交
505

506 507 508 509
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
S
Sandeep Somavarapu 已提交
510
		if (control) {
511

512 513 514 515 516 517 518
			// Hook Listener for Configuration changes
			this.activeEditorListeners.push(control.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
				if (event.accessibilitySupport) {
					this.onScreenReaderModeChange(control);
				}
			}));

519 520 521 522 523 524
			// Hook Listener for Selection changes
			this.activeEditorListeners.push(control.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(control);
			}));

			// Hook Listener for mode changes
A
Alex Dima 已提交
525
			this.activeEditorListeners.push(control.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
526 527 528 529
				this.onModeChange(control);
			}));

			// Hook Listener for content changes
530
			this.activeEditorListeners.push(control.onDidChangeModelContent((e) => {
531 532 533 534 535 536 537
				this.onEOLChange(control);
			}));

			// Hook Listener for content options changes
			this.activeEditorListeners.push(control.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
				this.onIndentationChange(control);
			}));
E
Erich Gamma 已提交
538
		}
539 540 541

		// Handle binary editors
		else if (activeEditor instanceof BaseBinaryResourceEditor || activeEditor instanceof BinaryResourceDiffEditor) {
542
			const binaryEditors: BaseBinaryResourceEditor[] = [];
543
			if (activeEditor instanceof BinaryResourceDiffEditor) {
544 545 546 547 548 549 550 551 552
				const details = activeEditor.getDetailsEditor();
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

				const master = activeEditor.getMasterEditor();
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
553
			} else {
554
				binaryEditors.push(activeEditor);
555 556 557 558 559 560 561
			}

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
					this.onMetadataChange(activeEditor);
				}));
			});
562
		}
563
	}
E
Erich Gamma 已提交
564

A
Alex Dima 已提交
565
	private onModeChange(editorWidget: ICommonCodeEditor): void {
566
		let info: StateDelta = { mode: null };
E
Erich Gamma 已提交
567 568

		// We only support text based editors
569
		if (editorWidget) {
A
Alex Dima 已提交
570
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
571 572
			if (textModel) {
				// Compute mode
A
Alex Dima 已提交
573
				const modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
574
				info = { mode: this.modeService.getLanguageName(modeId) };
E
Erich Gamma 已提交
575 576 577 578 579 580
			}
		}

		this.updateState(info);
	}

A
Alex Dima 已提交
581
	private onIndentationChange(editorWidget: ICommonCodeEditor): void {
582
		const update: StateDelta = { indentation: null };
583

584
		if (editorWidget) {
A
Alex Dima 已提交
585
			const model = editorWidget.getModel();
586 587 588 589 590 591 592
			if (model) {
				const modelOpts = model.getOptions();
				update.indentation = (
					modelOpts.insertSpaces
						? nls.localize('spacesSize', "Spaces: {0}", modelOpts.tabSize)
						: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
				);
593
			}
I
isidor 已提交
594
		}
595

I
isidor 已提交
596 597 598
		this.updateState(update);
	}

599 600 601 602 603 604 605 606 607 608
	private onMetadataChange(editor: IBaseEditor): void {
		const update: StateDelta = { metadata: null };

		if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) {
			update.metadata = editor.getMetadata();
		}

		this.updateState(update);
	}

609 610 611 612 613 614 615 616 617 618 619 620
	private onScreenReaderModeChange(editorWidget: ICommonCodeEditor): void {
		let screenReaderMode = false;

		// We only support text based editors
		if (editorWidget) {

			screenReaderMode = (editorWidget.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled);
		}

		this.updateState({ screenReaderMode: screenReaderMode });
	}

A
Alex Dima 已提交
621
	private onSelectionChange(editorWidget: ICommonCodeEditor): void {
B
Benjamin Pasero 已提交
622
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
623 624

		// We only support text based editors
625
		if (editorWidget) {
E
Erich Gamma 已提交
626 627 628 629 630 631

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
632
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
633
			if (textModel) {
634
				info.selections.forEach(selection => {
E
Erich Gamma 已提交
635 636 637 638 639 640
					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) {
B
Benjamin Pasero 已提交
641
				const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition());
E
Erich Gamma 已提交
642

643 644 645 646 647 648 649
				let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor
				selectionClone = new Selection(
					selectionClone.selectionStartLineNumber,
					selectionClone.selectionStartColumn,
					selectionClone.positionLineNumber,
					visibleColumn
				);
E
Erich Gamma 已提交
650 651 652 653 654

				info.selections[0] = selectionClone;
			}
		}

655
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
656 657
	}

A
Alex Dima 已提交
658
	private onEOLChange(editorWidget: ICommonCodeEditor): void {
B
Benjamin Pasero 已提交
659
		const info: StateDelta = { EOL: null };
A
Alex Dima 已提交
660

A
Alex Dima 已提交
661 662
		if (editorWidget && !editorWidget.getConfiguration().readOnly) {
			const codeEditorModel = editorWidget.getModel();
I
isidor 已提交
663 664 665
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
666 667 668 669 670
		}

		this.updateState(info);
	}

671
	private onEncodingChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
672 673 674 675
		if (e && !this.isActiveEditor(e)) {
			return;
		}

B
Benjamin Pasero 已提交
676
		const info: StateDelta = { encoding: null };
E
Erich Gamma 已提交
677 678

		// We only support text based editors
S
Sandeep Somavarapu 已提交
679
		if (getEditorWidget(e)) {
680 681
			const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
			if (encodingSupport) {
B
Benjamin Pasero 已提交
682 683
				const rawEncoding = encodingSupport.getEncoding();
				const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
684 685 686 687 688 689 690 691 692 693 694
				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);
	}

695
	private onResourceEncodingChange(resource: uri): void {
B
Benjamin Pasero 已提交
696
		const activeEditor = this.editorService.getActiveEditor();
697
		if (activeEditor) {
698
			const activeResource = toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] });
B
Benjamin Pasero 已提交
699
			if (activeResource && activeResource.toString() === resource.toString()) {
700
				return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
701 702
			}
		}
E
Erich Gamma 已提交
703 704
	}

705
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
706
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
707 708 709 710

		this.updateState(info);
	}

711
	private isActiveEditor(e: IBaseEditor): boolean {
B
Benjamin Pasero 已提交
712
		const activeEditor = this.editorService.getActiveEditor();
E
Erich Gamma 已提交
713 714 715 716 717

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

A
Alex Dima 已提交
718 719
function isWritableCodeEditor(codeEditor: ICommonCodeEditor): boolean {
	if (!codeEditor) {
S
Sandeep Somavarapu 已提交
720 721
		return false;
	}
A
Alex Dima 已提交
722 723 724
	const config = codeEditor.getConfiguration();
	return (!config.readOnly);
}
S
Sandeep Somavarapu 已提交
725

A
Alex Dima 已提交
726 727
function isWritableBaseEditor(e: IBaseEditor): boolean {
	return isWritableCodeEditor(getEditorWidget(e));
E
Erich Gamma 已提交
728 729
}

730 731
export class ShowLanguageExtensionsAction extends Action {

732
	static ID = 'workbench.action.showLanguageExtensions';
733 734

	constructor(
735 736
		private fileExtension: string,
		@ICommandService private commandService: ICommandService,
737 738
		@IExtensionGalleryService galleryService: IExtensionGalleryService
	) {
739 740
		super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));

741 742 743 744
		this.enabled = galleryService.isEnabled();
	}

	run(): TPromise<void> {
745
		return this.commandService.executeCommand('workbench.extensions.action.showLanguageExtensions', this.fileExtension).then(() => void 0);
746 747 748
	}
}

E
Erich Gamma 已提交
749 750 751 752 753
export class ChangeModeAction extends Action {

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

754 755
	private static FILE_ASSOCIATION_KEY = 'files.associations';

E
Erich Gamma 已提交
756 757 758 759
	constructor(
		actionId: string,
		actionLabel: string,
		@IModeService private modeService: IModeService,
760
		@IModelService private modelService: IModelService,
E
Erich Gamma 已提交
761
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
762
		@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService,
763
		@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
764
		@IQuickOpenService private quickOpenService: IQuickOpenService,
765 766 767 768
		@IPreferencesService private preferencesService: IPreferencesService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@ICommandService private commandService: ICommandService,
		@IConfigurationEditingService private configurationEditService: IConfigurationEditingService
E
Erich Gamma 已提交
769 770 771 772
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
773
	public run(): TPromise<any> {
E
Erich Gamma 已提交
774
		let activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
775 776
		const editorWidget = getEditorWidget(activeEditor);
		if (!editorWidget) {
E
Erich Gamma 已提交
777 778 779
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

A
Alex Dima 已提交
780
		const textModel = editorWidget.getModel();
781
		const fileResource = toResource(activeEditor.input, { supportSideBySide: true, filter: 'file' });
E
Erich Gamma 已提交
782 783 784

		// Compute mode
		let currentModeId: string;
785
		let modeId;
786
		if (textModel) {
787
			modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
788
			currentModeId = this.modeService.getLanguageName(modeId);
E
Erich Gamma 已提交
789 790 791
		}

		// All languages are valid picks
792
		const languages = this.modeService.getRegisteredLanguageNames();
B
Benjamin Pasero 已提交
793
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
794 795 796 797 798 799
			let description: string;
			if (currentModeId === lang) {
				description = nls.localize('languageDescription', "({0}) - Configured Language", this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
			} else {
				description = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
			}
800

801 802 803 804 805 806 807 808 809 810 811 812 813
			// construct a fake resource to be able to show nice icons if any
			let fakeResource: uri;
			const extensions = this.modeService.getExtensions(lang);
			if (extensions && extensions.length) {
				fakeResource = uri.file(extensions[0]);
			} else {
				const filenames = this.modeService.getFilenames(lang);
				if (filenames && filenames.length) {
					fakeResource = uri.file(filenames[0]);
				}
			}

			return <IFilePickOpenEntry>{
814
				label: lang,
815
				resource: fakeResource,
B
Benjamin Pasero 已提交
816
				description
E
Erich Gamma 已提交
817 818
			};
		});
819

820
		if (fileResource) {
B
Benjamin Pasero 已提交
821
			picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages (identifier)") };
822
		}
E
Erich Gamma 已提交
823

824
		// Offer action to configure via settings
825
		let configureModeAssociations: IPickOpenEntry;
826
		let configureModeSettings: IPickOpenEntry;
827
		let galleryAction: Action;
828 829
		if (fileResource) {
			const ext = paths.extname(fileResource.fsPath) || paths.basename(fileResource.fsPath);
830 831 832 833 834

			galleryAction = this.instantiationService.createInstance(ShowLanguageExtensionsAction, ext);
			if (galleryAction.enabled) {
				picks.unshift(galleryAction);
			}
835

836 837
			configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
			picks.unshift(configureModeSettings);
838
			configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
839 840
			picks.unshift(configureModeAssociations);
		}
E
Erich Gamma 已提交
841

842
		// Offer to "Auto Detect"
B
Benjamin Pasero 已提交
843
		const autoDetectMode: IPickOpenEntry = {
844 845
			label: nls.localize('autoDetect', "Auto Detect")
		};
846
		if (fileResource) {
847 848
			picks.unshift(autoDetectMode);
		}
849

850 851 852 853
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode") }).then(pick => {
			if (!pick) {
				return;
			}
B
Benjamin Pasero 已提交
854

855 856 857 858
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
859

860 861
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
862
				this.configureFileAssociation(fileResource);
863 864
				return;
			}
865

866 867
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
868
				this.preferencesService.configureSettingsForLanguage(modeId);
869 870 871
				return;
			}

872 873
			// Change mode for active editor
			activeEditor = this.editorService.getActiveEditor();
A
Alex Dima 已提交
874
			const editorWidget = getEditorWidget(activeEditor);
S
Sandeep Somavarapu 已提交
875
			if (editorWidget) {
876
				const models: IModel[] = [];
877

A
Alex Dima 已提交
878
				const textModel = editorWidget.getModel();
879 880 881
				if (textModel) {
					models.push(textModel);
				}
882

883 884 885
				// Find mode
				let mode: TPromise<IMode>;
				if (pick === autoDetectMode) {
886
					mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] }).fsPath, textModel.getLineContent(1));
887 888
				} else {
					mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
E
Erich Gamma 已提交
889
				}
890 891 892 893 894

				// Change mode
				models.forEach(textModel => {
					this.modelService.setMode(textModel, mode);
				});
E
Erich Gamma 已提交
895 896 897
			}
		});
	}
898 899

	private configureFileAssociation(resource: uri): void {
900 901 902 903
		const extension = paths.extname(resource.fsPath);
		const basename = paths.basename(resource.fsPath);
		const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);

904 905
		const languages = this.modeService.getRegisteredLanguageNames();
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
906 907
			const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());

908
			return <IPickOpenEntry>{
909 910 911
				id,
				label: lang,
				description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0
912 913 914 915 916 917
			};
		});

		TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => {
			this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => {
				if (language) {
918
					const fileAssociationsConfig = this.configurationService.lookup(ChangeModeAction.FILE_ASSOCIATION_KEY);
919 920 921 922 923 924 925 926

					let associationKey: string;
					if (extension && basename[0] !== '.') {
						associationKey = `*${extension}`; // only use "*.ext" if the file path is in the form of <name>.<ext>
					} else {
						associationKey = basename; // otherwise use the basename (e.g. .gitignore, Dockerfile)
					}

927 928 929 930 931 932 933 934 935 936 937
					// If the association is already being made in the workspace, make sure to target workspace settings
					let target = ConfigurationTarget.USER;
					if (fileAssociationsConfig.workspace && !!fileAssociationsConfig.workspace[associationKey]) {
						target = ConfigurationTarget.WORKSPACE;
					}

					// Make sure to write into the value of the target and not the merged value from USER and WORKSPACE config
					let currentAssociations = (target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user;
					if (!currentAssociations) {
						currentAssociations = Object.create(null);
					}
938

939 940
					currentAssociations[associationKey] = language.id;

941
					// Write config
S
Sandeep Somavarapu 已提交
942
					this.configurationEditingService.writeConfiguration(target, { key: ChangeModeAction.FILE_ASSOCIATION_KEY, value: currentAssociations });
943 944 945 946
				}
			});
		});
	}
E
Erich Gamma 已提交
947 948 949 950 951 952
}

export interface IChangeEOLEntry extends IPickOpenEntry {
	eol: EndOfLineSequence;
}

I
isidor 已提交
953
class ChangeIndentationAction extends Action {
I
isidor 已提交
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968

	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();
S
Sandeep Somavarapu 已提交
969 970
		const control = getEditorWidget(activeEditor);
		if (!control) {
I
isidor 已提交
971 972
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
A
Alex Dima 已提交
973
		if (!isWritableCodeEditor(control)) {
I
isidor 已提交
974 975
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}
976

B
Benjamin Pasero 已提交
977 978 979 980 981 982 983
		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)
A
Alex Dima 已提交
984
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
985 986
			return {
				id: a.id,
B
Benjamin Pasero 已提交
987
				label: a.label,
A
Alex Dima 已提交
988
				detail: (language === LANGUAGE_DEFAULT) ? null : a.alias,
989 990 991 992
				run: () => {
					control.focus();
					a.run();
				}
B
Benjamin Pasero 已提交
993 994 995
			};
		});

B
Benjamin Pasero 已提交
996
		(<IPickOpenEntry>picks[0]).separator = { label: nls.localize('indentView', "change view") };
997
		(<IPickOpenEntry>picks[3]).separator = { label: nls.localize('indentConvert', "convert file"), border: true };
I
isidor 已提交
998

B
Benjamin Pasero 已提交
999
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
I
isidor 已提交
1000 1001 1002
	}
}

E
Erich Gamma 已提交
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
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 已提交
1017
	public run(): TPromise<any> {
E
Erich Gamma 已提交
1018
		let activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
1019 1020
		const editorWidget = getEditorWidget(activeEditor);
		if (!editorWidget) {
E
Erich Gamma 已提交
1021 1022 1023
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

A
Alex Dima 已提交
1024
		if (!isWritableCodeEditor(editorWidget)) {
E
Erich Gamma 已提交
1025 1026 1027
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}

A
Alex Dima 已提交
1028
		const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
1029

B
Benjamin Pasero 已提交
1030
		const EOLOptions: IChangeEOLEntry[] = [
1031 1032
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1033 1034
		];

1035
		const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1;
E
Erich Gamma 已提交
1036

1037
		return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
E
Erich Gamma 已提交
1038 1039
			if (eol) {
				activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
1040
				const editorWidget = getEditorWidget(activeEditor);
A
Alex Dima 已提交
1041 1042
				if (editorWidget && isWritableCodeEditor(editorWidget)) {
					const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
					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,
1060 1061
		@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
		@IFileService private fileService: IFileService
E
Erich Gamma 已提交
1062 1063 1064 1065
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
1066
	public run(): TPromise<any> {
E
Erich Gamma 已提交
1067
		let activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
1068
		if (!getEditorWidget(activeEditor) || !activeEditor.input) {
E
Erich Gamma 已提交
1069 1070 1071
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

1072 1073
		let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeEditor.input);
		if (!encodingSupport) {
E
Erich Gamma 已提交
1074 1075 1076 1077
			return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
		}

		let pickActionPromise: TPromise<IPickOpenEntry>;
B
Benjamin Pasero 已提交
1078 1079 1080 1081 1082 1083 1084

		let saveWithEncodingPick: IPickOpenEntry;
		let reopenWithEncodingPick: IPickOpenEntry;
		if (language === LANGUAGE_DEFAULT) {
			saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
			reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
		} else {
B
Benjamin Pasero 已提交
1085 1086
			saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding"), detail: 'Save with Encoding', };
			reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' };
B
Benjamin Pasero 已提交
1087
		}
E
Erich Gamma 已提交
1088

1089
		if (encodingSupport instanceof UntitledEditorInput) {
A
Alex Dima 已提交
1090
			pickActionPromise = TPromise.as(saveWithEncodingPick);
A
Alex Dima 已提交
1091
		} else if (!isWritableBaseEditor(activeEditor)) {
A
Alex Dima 已提交
1092
			pickActionPromise = TPromise.as(reopenWithEncodingPick);
E
Erich Gamma 已提交
1093
		} else {
B
Benjamin Pasero 已提交
1094
			pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
E
Erich Gamma 已提交
1095 1096
		}

1097
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1098
			if (!action) {
1099
				return undefined;
E
Erich Gamma 已提交
1100 1101
			}

1102 1103 1104 1105 1106
			const resource = toResource(activeEditor.input, { filter: 'file', supportSideBySide: true });
			if (!resource) {
				return TPromise.as(null);
			}

1107
			return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
1108 1109 1110
				.then(() => {
					return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
				})
B
Benjamin Pasero 已提交
1111
				.then((guessedEncoding: string) => {
1112
					const isReopenWithEncoding = (action === reopenWithEncodingPick);
1113
					const configuredEncoding = this.configurationService.lookup('files.encoding', { resource }).value;
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
					let directMatchIndex: number;
					let aliasMatchIndex: number;

					// All encodings are valid picks
					const picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS)
						.sort((k1, k2) => {
							if (k1 === configuredEncoding) {
								return -1;
							} else if (k2 === configuredEncoding) {
								return 1;
							}

							return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order;
						})
						.filter(k => {
B
Benjamin Pasero 已提交
1129 1130 1131 1132
							if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
								return false; // do not show encoding if it is the guessed encoding that does not match the configured
							}

1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
							return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode
						})
						.map((key, index) => {
							if (key === encodingSupport.getEncoding()) {
								directMatchIndex = index;
							} else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) {
								aliasMatchIndex = index;
							}

							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong };
						});

B
Benjamin Pasero 已提交
1145 1146
					// If we have a guessed encoding, show it first unless it matches the configured encoding
					if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
1147
						picks[0].separator = { border: true };
B
Benjamin Pasero 已提交
1148
						picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
1149
					}
1150

1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
					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"),
						autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
					}).then(encoding => {
						if (encoding) {
							activeEditor = this.editorService.getActiveEditor();
							encodingSupport = toEditorWithEncodingSupport(activeEditor.input);
							if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
								encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
							}
1161
						}
1162
					});
E
Erich Gamma 已提交
1163 1164 1165
				});
		});
	}
J
Johannes Rieken 已提交
1166
}