editorStatus.ts 48.3 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import 'vs/css!./media/editorstatus';
9
import nls = require('vs/nls');
J
Johannes Rieken 已提交
10
import { TPromise } from 'vs/base/common/winjs.base';
11
import { $, append, runAtThisOrScheduleAtNextAnimationFrame, addDisposableListener, getDomNodePagePosition } from 'vs/base/browser/dom';
12 13 14
import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import types = require('vs/base/common/types');
E
Erich Gamma 已提交
15
import uri from 'vs/base/common/uri';
16
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';
20
import * as browser from 'vs/base/browser/browser';
J
Johannes Rieken 已提交
21 22
import { IMode } from 'vs/editor/common/modes';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
23
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
J
Johannes Rieken 已提交
24 25
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
26
import { IEditorAction } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
27
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
28
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
29 30
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations';
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
J
Johannes Rieken 已提交
31 32
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
33
import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/editor';
J
Johannes Rieken 已提交
34
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Johannes Rieken 已提交
35
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
J
Johannes Rieken 已提交
36
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
37
import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
38 39 40 41 42 43
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';
44
import { ICommandService } from 'vs/platform/commands/common/commands';
45
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
46
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
47
import { getCodeEditor as getEditorWidget, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
48
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
49
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
50
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
51 52
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
53 54
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
55 56 57
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
58 59
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
60 61
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { Themable } from 'vs/workbench/common/theme';
62

B
Benjamin Pasero 已提交
63 64 65 66
// TODO@Sandeep layer breaker
// tslint:disable-next-line:import-patterns
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';

67 68 69 70 71
function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport {
	if (input instanceof SideBySideEditorInput) {
		input = input.master;
	}

72 73 74 75
	if (input instanceof UntitledEditorInput) {
		return input;
	}

76 77 78 79 80 81
	let encodingSupport = input as IFileEditorInput;
	if (types.areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
		return encodingSupport;
	}

	return null;
82 83
}

E
Erich Gamma 已提交
84
interface IEditorSelectionStatus {
85
	selections?: Selection[];
E
Erich Gamma 已提交
86 87 88
	charactersSelected?: number;
}

89
class StateChange {
A
Alex Dima 已提交
90
	_stateChangeBrand: void;
91

I
isidor 已提交
92
	indentation: boolean;
93 94 95 96
	selectionStatus: boolean;
	mode: boolean;
	encoding: boolean;
	EOL: boolean;
E
Erich Gamma 已提交
97
	tabFocusMode: boolean;
98
	screenReaderMode: boolean;
99
	metadata: boolean;
100 101 102 103 104 105 106 107

	constructor() {
		this.indentation = false;
		this.selectionStatus = false;
		this.mode = false;
		this.encoding = false;
		this.EOL = false;
		this.tabFocusMode = false;
108
		this.screenReaderMode = false;
109
		this.metadata = false;
110 111
	}

112
	public combine(other: StateChange) {
113 114 115 116 117 118
		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;
119
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
120
		this.metadata = this.metadata || other.metadata;
121
	}
E
Erich Gamma 已提交
122 123
}

124 125 126 127 128
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
129
	indentation?: string;
130
	tabFocusMode?: boolean;
131
	screenReaderMode?: boolean;
132
	metadata?: string;
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
}

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

151 152 153
	private _tabFocusMode: boolean;
	public get tabFocusMode(): boolean { return this._tabFocusMode; }

154 155 156
	private _screenReaderMode: boolean;
	public get screenReaderMode(): boolean { return this._screenReaderMode; }

157 158 159
	private _metadata: string;
	public get metadata(): string { return this._metadata; }

160 161 162 163 164 165
	constructor() {
		this._selectionStatus = null;
		this._mode = null;
		this._encoding = null;
		this._EOL = null;
		this._tabFocusMode = false;
166
		this._screenReaderMode = false;
167
		this._metadata = null;
168 169
	}

170
	public update(update: StateDelta): StateChange {
B
Benjamin Pasero 已提交
171
		const e = new StateChange();
172 173 174 175 176 177 178 179 180
		let somethingChanged = false;

		if (typeof update.selectionStatus !== 'undefined') {
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
				somethingChanged = true;
				e.selectionStatus = true;
			}
		}
I
isidor 已提交
181
		if (typeof update.indentation !== 'undefined') {
182 183
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
I
isidor 已提交
184 185 186 187
				somethingChanged = true;
				e.indentation = true;
			}
		}
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
		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;
			}
		}
216 217 218 219 220 221 222
		if (typeof update.screenReaderMode !== 'undefined') {
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
				somethingChanged = true;
				e.screenReaderMode = true;
			}
		}
223 224 225 226 227 228 229
		if (typeof update.metadata !== 'undefined') {
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
				somethingChanged = true;
				e.metadata = true;
			}
		}
230 231 232 233 234 235 236 237

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

238 239 240 241 242 243
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 已提交
244
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
245
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
A
Alex Dima 已提交
246
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
E
Erich Gamma 已提交
247

248
function setDisplay(el: HTMLElement, desiredValue: string): void {
A
Alex Dima 已提交
249 250 251 252
	if (el.style.display !== desiredValue) {
		el.style.display = desiredValue;
	}
}
253
function show(el: HTMLElement): void {
254
	setDisplay(el, '');
A
Alex Dima 已提交
255
}
256
function hide(el: HTMLElement): void {
257
	setDisplay(el, 'none');
A
Alex Dima 已提交
258 259
}

260
export class EditorStatus implements IStatusbarItem {
E
Erich Gamma 已提交
261

262
	private state: State;
263 264
	private element: HTMLElement;
	private tabFocusModeElement: HTMLElement;
265
	private screenRedearModeElement: HTMLElement;
I
isidor 已提交
266
	private indentationElement: HTMLElement;
267 268 269 270
	private selectionElement: HTMLElement;
	private encodingElement: HTMLElement;
	private eolElement: HTMLElement;
	private modeElement: HTMLElement;
271
	private metadataElement: HTMLElement;
E
Erich Gamma 已提交
272
	private toDispose: IDisposable[];
273
	private activeEditorListeners: IDisposable[];
274 275
	private delayedRender: IDisposable;
	private toRender: StateChange;
276
	private screenReaderExplanation: ScreenReaderDetectedExplanation;
E
Erich Gamma 已提交
277

278 279
	constructor(
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
280
		@IEditorGroupService private editorGroupService: IEditorGroupService,
281 282
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IInstantiationService private instantiationService: IInstantiationService,
283
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
284
		@IModeService private modeService: IModeService,
285 286
		@ITextFileService private textFileService: ITextFileService,
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
287 288
	) {
		this.toDispose = [];
289
		this.activeEditorListeners = [];
290
		this.state = new State();
E
Erich Gamma 已提交
291 292
	}

293 294 295
	public render(container: HTMLElement): IDisposable {
		this.element = append(container, $('.editor-statusbar-item'));

296
		this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
297 298 299
		this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode");
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;
300
		hide(this.tabFocusModeElement);
301

302 303
		this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info'));
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
304
		this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle;
305
		this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();
306 307
		hide(this.screenRedearModeElement);

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

313 314 315 316 317
		this.indentationElement = append(this.element, $('a.editor-status-indentation'));
		this.indentationElement.title = nls.localize('indentation', "Indentation");
		this.indentationElement.onclick = () => this.onIndentationClick();
		hide(this.indentationElement);

318 319 320
		this.encodingElement = append(this.element, $('a.editor-status-encoding'));
		this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding");
		this.encodingElement.onclick = () => this.onEncodingClick();
321
		hide(this.encodingElement);
322 323 324 325

		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();
326
		hide(this.eolElement);
327 328 329 330

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

333 334 335 336
		this.metadataElement = append(this.element, $('span.editor-status-metadata'));
		this.metadataElement.title = nls.localize('fileInfo', "File Information");
		hide(this.metadataElement);

337 338 339
		this.delayedRender = null;
		this.toRender = null;

340
		this.toDispose.push(
341 342 343 344 345 346 347 348
			{
				dispose: () => {
					if (this.delayedRender) {
						this.delayedRender.dispose();
						this.delayedRender = null;
					}
				}
			},
349
			this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()),
350 351
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
352
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
353
		);
E
Erich Gamma 已提交
354

J
Joao Moreno 已提交
355
		return combinedDisposable(this.toDispose);
356 357
	}

358
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
359
		const changed = this.state.update(update);
360 361 362
		if (!changed) {
			// Nothing really changed
			return;
E
Erich Gamma 已提交
363 364
		}

365 366 367 368
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
369
				const toRender = this.toRender;
370 371 372 373 374 375 376 377
				this.toRender = null;
				this._renderNow(toRender);
			});
		} else {
			this.toRender.combine(changed);
		}
	}

378
	private _renderNow(changed: StateChange): void {
379 380 381 382 383 384
		if (changed.tabFocusMode) {
			if (this.state.tabFocusMode && this.state.tabFocusMode === true) {
				show(this.tabFocusModeElement);
			} else {
				hide(this.tabFocusModeElement);
			}
E
Erich Gamma 已提交
385 386
		}

387 388 389 390 391 392 393 394
		if (changed.screenReaderMode) {
			if (this.state.screenReaderMode && this.state.screenReaderMode === true) {
				show(this.screenRedearModeElement);
			} else {
				hide(this.screenRedearModeElement);
			}
		}

I
isidor 已提交
395 396 397 398 399 400 401 402 403
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

404
		if (changed.selectionStatus) {
405
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
406 407 408 409 410
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
411 412
		}

413 414 415 416 417 418 419
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
420 421
		}

422 423 424 425 426 427 428
		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 已提交
429 430
		}

431 432 433 434 435 436 437 438
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
439 440 441 442 443 444 445 446 447

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

450
	private getSelectionLabel(info: IEditorSelectionStatus): string {
E
Erich Gamma 已提交
451 452 453 454 455 456
		if (!info || !info.selections) {
			return null;
		}

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

			return strings.format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
461
		}
B
Benjamin Pasero 已提交
462 463 464 465 466 467 468 469 470 471

		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 已提交
472 473
	}

474
	private onModeClick(): void {
B
Benjamin Pasero 已提交
475
		const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
476 477 478 479 480

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

I
isidor 已提交
481 482 483 484 485 486
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

487
	private onScreenReaderModeClick(): void {
488 489 490 491 492 493 494 495 496 497 498 499
		const showExplanation = !this.screenReaderExplanation || !this.screenReaderExplanation.visible;

		if (!this.screenReaderExplanation) {
			this.screenReaderExplanation = this.instantiationService.createInstance(ScreenReaderDetectedExplanation);
			this.toDispose.push(this.screenReaderExplanation);
		}

		if (showExplanation) {
			this.screenReaderExplanation.show(this.screenRedearModeElement);
		} else {
			this.screenReaderExplanation.hide();
		}
500 501
	}

502 503 504 505 506
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
B
Benjamin Pasero 已提交
507
		const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
508 509 510 511 512 513

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

	private onEncodingClick(): void {
B
Benjamin Pasero 已提交
514
		const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
515 516 517 518 519 520

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

	private onTabFocusModeClick(): void {
521
		TabFocus.setTabFocusMode(false);
522 523
	}

524
	private onEditorsChanged(): void {
525
		const activeEditor = this.editorService.getActiveEditor();
A
Alex Dima 已提交
526
		const control = getEditorWidget(activeEditor);
527 528

		// Update all states
529
		this.onScreenReaderModeChange(control);
530 531 532
		this.onSelectionChange(control);
		this.onModeChange(control);
		this.onEOLChange(control);
B
Benjamin Pasero 已提交
533
		this.onEncodingChange(activeEditor);
534
		this.onIndentationChange(control);
535
		this.onMetadataChange(activeEditor);
E
Erich Gamma 已提交
536

537 538 539 540
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

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

543 544 545 546 547 548 549
			// Hook Listener for Configuration changes
			this.activeEditorListeners.push(control.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
				if (event.accessibilitySupport) {
					this.onScreenReaderModeChange(control);
				}
			}));

550 551 552 553 554 555
			// Hook Listener for Selection changes
			this.activeEditorListeners.push(control.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(control);
			}));

			// Hook Listener for mode changes
A
Alex Dima 已提交
556
			this.activeEditorListeners.push(control.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
557 558 559 560
				this.onModeChange(control);
			}));

			// Hook Listener for content changes
561
			this.activeEditorListeners.push(control.onDidChangeModelContent((e) => {
562 563 564 565 566 567 568
				this.onEOLChange(control);
			}));

			// Hook Listener for content options changes
			this.activeEditorListeners.push(control.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
				this.onIndentationChange(control);
			}));
E
Erich Gamma 已提交
569
		}
570 571 572

		// Handle binary editors
		else if (activeEditor instanceof BaseBinaryResourceEditor || activeEditor instanceof BinaryResourceDiffEditor) {
573
			const binaryEditors: BaseBinaryResourceEditor[] = [];
574
			if (activeEditor instanceof BinaryResourceDiffEditor) {
575 576 577 578 579 580 581 582 583
				const details = activeEditor.getDetailsEditor();
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

				const master = activeEditor.getMasterEditor();
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
584
			} else {
585
				binaryEditors.push(activeEditor);
586 587 588 589 590 591 592
			}

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

596
	private onModeChange(editorWidget: ICodeEditor): void {
597
		let info: StateDelta = { mode: null };
E
Erich Gamma 已提交
598 599

		// We only support text based editors
600
		if (editorWidget) {
A
Alex Dima 已提交
601
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
602 603
			if (textModel) {
				// Compute mode
A
Alex Dima 已提交
604
				const modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
605
				info = { mode: this.modeService.getLanguageName(modeId) };
E
Erich Gamma 已提交
606 607 608 609 610 611
			}
		}

		this.updateState(info);
	}

612
	private onIndentationChange(editorWidget: ICodeEditor): void {
613
		const update: StateDelta = { indentation: null };
614

615
		if (editorWidget) {
A
Alex Dima 已提交
616
			const model = editorWidget.getModel();
617 618 619 620 621 622 623
			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)
				);
624
			}
I
isidor 已提交
625
		}
626

I
isidor 已提交
627 628 629
		this.updateState(update);
	}

630 631 632 633 634 635 636 637 638 639
	private onMetadataChange(editor: IBaseEditor): void {
		const update: StateDelta = { metadata: null };

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

		this.updateState(update);
	}

640 641
	private _promptedScreenReader: boolean = false;

642
	private onScreenReaderModeChange(editorWidget: ICodeEditor): void {
643 644 645 646
		let screenReaderMode = false;

		// We only support text based editors
		if (editorWidget) {
647 648
			const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled);
			if (screenReaderDetected) {
649
				const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
650 651 652 653 654 655 656 657 658 659
				if (screenReaderConfiguration === 'auto') {
					// show explanation
					if (!this._promptedScreenReader) {
						this._promptedScreenReader = true;
						setTimeout(() => {
							this.onScreenReaderModeClick();
						}, 100);
					}
				}
			}
660 661 662 663

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

664 665
		if (screenReaderMode === false && this.screenReaderExplanation && this.screenReaderExplanation.visible) {
			this.screenReaderExplanation.hide();
666 667
		}

668 669 670
		this.updateState({ screenReaderMode: screenReaderMode });
	}

671
	private onSelectionChange(editorWidget: ICodeEditor): void {
B
Benjamin Pasero 已提交
672
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
673 674

		// We only support text based editors
675
		if (editorWidget) {
E
Erich Gamma 已提交
676 677 678 679 680 681

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
682
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
683
			if (textModel) {
684
				info.selections.forEach(selection => {
E
Erich Gamma 已提交
685 686 687 688 689 690
					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 已提交
691
				const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition());
E
Erich Gamma 已提交
692

693 694 695 696 697 698 699
				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 已提交
700 701 702 703 704

				info.selections[0] = selectionClone;
			}
		}

705
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
706 707
	}

708
	private onEOLChange(editorWidget: ICodeEditor): void {
B
Benjamin Pasero 已提交
709
		const info: StateDelta = { EOL: null };
A
Alex Dima 已提交
710

A
Alex Dima 已提交
711 712
		if (editorWidget && !editorWidget.getConfiguration().readOnly) {
			const codeEditorModel = editorWidget.getModel();
I
isidor 已提交
713 714 715
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
716 717 718 719 720
		}

		this.updateState(info);
	}

721
	private onEncodingChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
722 723 724 725
		if (e && !this.isActiveEditor(e)) {
			return;
		}

B
Benjamin Pasero 已提交
726
		const info: StateDelta = { encoding: null };
E
Erich Gamma 已提交
727 728

		// We only support text based editors
S
Sandeep Somavarapu 已提交
729
		if (getEditorWidget(e)) {
730 731
			const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
			if (encodingSupport) {
B
Benjamin Pasero 已提交
732 733
				const rawEncoding = encodingSupport.getEncoding();
				const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
734 735 736 737 738 739 740 741 742 743 744
				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);
	}

745
	private onResourceEncodingChange(resource: uri): void {
B
Benjamin Pasero 已提交
746
		const activeEditor = this.editorService.getActiveEditor();
747
		if (activeEditor) {
748
			const activeResource = toResource(activeEditor.input, { supportSideBySide: true });
B
Benjamin Pasero 已提交
749
			if (activeResource && activeResource.toString() === resource.toString()) {
750
				return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
751 752
			}
		}
E
Erich Gamma 已提交
753 754
	}

755
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
756
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
757 758 759 760

		this.updateState(info);
	}

761
	private isActiveEditor(e: IBaseEditor): boolean {
B
Benjamin Pasero 已提交
762
		const activeEditor = this.editorService.getActiveEditor();
E
Erich Gamma 已提交
763 764 765 766 767

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

768
function isWritableCodeEditor(codeEditor: ICodeEditor): boolean {
A
Alex Dima 已提交
769
	if (!codeEditor) {
S
Sandeep Somavarapu 已提交
770 771
		return false;
	}
A
Alex Dima 已提交
772 773 774
	const config = codeEditor.getConfiguration();
	return (!config.readOnly);
}
S
Sandeep Somavarapu 已提交
775

A
Alex Dima 已提交
776 777
function isWritableBaseEditor(e: IBaseEditor): boolean {
	return isWritableCodeEditor(getEditorWidget(e));
E
Erich Gamma 已提交
778 779
}

780 781
export class ShowLanguageExtensionsAction extends Action {

782
	static readonly ID = 'workbench.action.showLanguageExtensions';
783 784

	constructor(
785 786
		private fileExtension: string,
		@ICommandService private commandService: ICommandService,
787 788
		@IExtensionGalleryService galleryService: IExtensionGalleryService
	) {
789 790
		super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));

791 792 793 794
		this.enabled = galleryService.isEnabled();
	}

	run(): TPromise<void> {
S
Sandeep Somavarapu 已提交
795
		return this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension).then(() => void 0);
796 797 798
	}
}

E
Erich Gamma 已提交
799 800
export class ChangeModeAction extends Action {

M
Matt Bierner 已提交
801 802
	public static readonly ID = 'workbench.action.editor.changeLanguageMode';
	public static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
E
Erich Gamma 已提交
803 804 805 806 807

	constructor(
		actionId: string,
		actionLabel: string,
		@IModeService private modeService: IModeService,
808
		@IModelService private modelService: IModelService,
E
Erich Gamma 已提交
809
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
810
		@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
811
		@IQuickOpenService private quickOpenService: IQuickOpenService,
812 813
		@IPreferencesService private preferencesService: IPreferencesService,
		@IInstantiationService private instantiationService: IInstantiationService,
814
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService
E
Erich Gamma 已提交
815 816 817 818
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
819
	public run(): TPromise<any> {
E
Erich Gamma 已提交
820
		let activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
821 822
		const editorWidget = getEditorWidget(activeEditor);
		if (!editorWidget) {
E
Erich Gamma 已提交
823 824 825
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

A
Alex Dima 已提交
826
		const textModel = editorWidget.getModel();
B
Benjamin Pasero 已提交
827 828 829
		const resource = toResource(activeEditor.input, { supportSideBySide: true });

		let hasLanguageSupport = !!resource;
830
		if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
B
Benjamin Pasero 已提交
831
			hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
832
		}
E
Erich Gamma 已提交
833 834 835

		// Compute mode
		let currentModeId: string;
836
		let modeId: string;
837
		if (textModel) {
838
			modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
839
			currentModeId = this.modeService.getLanguageName(modeId);
E
Erich Gamma 已提交
840 841 842
		}

		// All languages are valid picks
843
		const languages = this.modeService.getRegisteredLanguageNames();
B
Benjamin Pasero 已提交
844
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
845 846 847 848 849 850
			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()));
			}
851

852 853 854 855 856 857 858 859 860 861 862 863 864
			// 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>{
865
				label: lang,
866
				resource: fakeResource,
B
Benjamin Pasero 已提交
867
				description
E
Erich Gamma 已提交
868 869
			};
		});
870

B
Benjamin Pasero 已提交
871
		if (hasLanguageSupport) {
B
Benjamin Pasero 已提交
872
			picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages (identifier)") };
873
		}
E
Erich Gamma 已提交
874

875
		// Offer action to configure via settings
876
		let configureModeAssociations: IPickOpenEntry;
877
		let configureModeSettings: IPickOpenEntry;
878
		let galleryAction: Action;
B
Benjamin Pasero 已提交
879
		if (hasLanguageSupport) {
880
			const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
S
Sandeep Somavarapu 已提交
881 882 883 884 885 886

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

887 888
			configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
			picks.unshift(configureModeSettings);
889
			configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
890 891
			picks.unshift(configureModeAssociations);
		}
E
Erich Gamma 已提交
892

893
		// Offer to "Auto Detect"
B
Benjamin Pasero 已提交
894
		const autoDetectMode: IPickOpenEntry = {
895 896
			label: nls.localize('autoDetect', "Auto Detect")
		};
897

B
Benjamin Pasero 已提交
898
		if (hasLanguageSupport) {
899 900
			picks.unshift(autoDetectMode);
		}
901

902
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
903 904 905
			if (!pick) {
				return;
			}
B
Benjamin Pasero 已提交
906

907 908 909 910
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
911

912 913
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
914
				this.configureFileAssociation(resource);
915 916
				return;
			}
917

918 919
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
920
				this.preferencesService.configureSettingsForLanguage(modeId);
921 922 923
				return;
			}

924 925
			// Change mode for active editor
			activeEditor = this.editorService.getActiveEditor();
926
			const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
A
Alex Dima 已提交
927
			const models: ITextModel[] = [];
928 929 930 931
			if (codeOrDiffEditor.codeEditor) {
				const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
				if (codeEditorModel) {
					models.push(codeEditorModel);
932
				}
933 934 935 936 937 938 939 940 941 942
			}
			if (codeOrDiffEditor.diffEditor) {
				const diffEditorModel = codeOrDiffEditor.diffEditor.getModel();
				if (diffEditorModel) {
					if (diffEditorModel.original) {
						models.push(diffEditorModel.original);
					}
					if (diffEditorModel.modified) {
						models.push(diffEditorModel.modified);
					}
E
Erich Gamma 已提交
943
				}
944
			}
945

946 947 948
			// Find mode
			let mode: TPromise<IMode>;
			if (pick === autoDetectMode) {
949
				mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
950 951
			} else {
				mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
E
Erich Gamma 已提交
952
			}
953 954 955 956 957

			// Change mode
			models.forEach(textModel => {
				this.modelService.setMode(textModel, mode);
			});
E
Erich Gamma 已提交
958 959
		});
	}
960 961

	private configureFileAssociation(resource: uri): void {
962 963 964 965
		const extension = paths.extname(resource.fsPath);
		const basename = paths.basename(resource.fsPath);
		const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);

966 967
		const languages = this.modeService.getRegisteredLanguageNames();
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
968 969
			const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());

970
			return <IPickOpenEntry>{
971 972 973
				id,
				label: lang,
				description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0
974 975 976 977 978 979
			};
		});

		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) {
980
					const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
981 982 983 984 985 986 987 988

					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)
					}

989 990 991 992 993 994 995
					// 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
J
Johannes Rieken 已提交
996
					let currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user);
997 998 999
					if (!currentAssociations) {
						currentAssociations = Object.create(null);
					}
1000

1001 1002
					currentAssociations[associationKey] = language.id;

1003
					this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
1004 1005 1006 1007
				}
			});
		});
	}
E
Erich Gamma 已提交
1008 1009 1010 1011 1012 1013
}

export interface IChangeEOLEntry extends IPickOpenEntry {
	eol: EndOfLineSequence;
}

I
isidor 已提交
1014
class ChangeIndentationAction extends Action {
I
isidor 已提交
1015

M
Matt Bierner 已提交
1016 1017
	public static readonly ID = 'workbench.action.editor.changeIndentation';
	public static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029

	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 已提交
1030 1031
		const control = getEditorWidget(activeEditor);
		if (!control) {
I
isidor 已提交
1032 1033
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
A
Alex Dima 已提交
1034
		if (!isWritableCodeEditor(control)) {
I
isidor 已提交
1035 1036
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}
1037

B
Benjamin Pasero 已提交
1038 1039 1040 1041 1042 1043 1044
		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 已提交
1045
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
1046 1047
			return {
				id: a.id,
B
Benjamin Pasero 已提交
1048
				label: a.label,
A
Alex Dima 已提交
1049
				detail: (language === LANGUAGE_DEFAULT) ? null : a.alias,
1050 1051 1052 1053
				run: () => {
					control.focus();
					a.run();
				}
B
Benjamin Pasero 已提交
1054 1055 1056
			};
		});

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

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

E
Erich Gamma 已提交
1064 1065
export class ChangeEOLAction extends Action {

M
Matt Bierner 已提交
1066 1067
	public static readonly ID = 'workbench.action.editor.changeEOL';
	public static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
E
Erich Gamma 已提交
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077

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

A
Alex Dima 已提交
1078
	public run(): TPromise<any> {
E
Erich Gamma 已提交
1079
		let activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
1080 1081
		const editorWidget = getEditorWidget(activeEditor);
		if (!editorWidget) {
E
Erich Gamma 已提交
1082 1083 1084
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

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

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

B
Benjamin Pasero 已提交
1091
		const EOLOptions: IChangeEOLEntry[] = [
1092 1093
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1094 1095
		];

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

1098
		return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
E
Erich Gamma 已提交
1099 1100
			if (eol) {
				activeEditor = this.editorService.getActiveEditor();
S
Sandeep Somavarapu 已提交
1101
				const editorWidget = getEditorWidget(activeEditor);
A
Alex Dima 已提交
1102 1103
				if (editorWidget && isWritableCodeEditor(editorWidget)) {
					const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
1104 1105 1106 1107 1108 1109 1110 1111 1112
					textModel.setEOL(eol.eol);
				}
			}
		});
	}
}

export class ChangeEncodingAction extends Action {

M
Matt Bierner 已提交
1113 1114
	public static readonly ID = 'workbench.action.editor.changeEncoding';
	public static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
E
Erich Gamma 已提交
1115 1116 1117 1118 1119 1120

	constructor(
		actionId: string,
		actionLabel: string,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
		@IQuickOpenService private quickOpenService: IQuickOpenService,
1121
		@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
1122
		@IFileService private fileService: IFileService
E
Erich Gamma 已提交
1123 1124 1125 1126
	) {
		super(actionId, actionLabel);
	}

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

1133 1134
		let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeEditor.input);
		if (!encodingSupport) {
E
Erich Gamma 已提交
1135 1136 1137 1138
			return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
		}

		let pickActionPromise: TPromise<IPickOpenEntry>;
B
Benjamin Pasero 已提交
1139 1140 1141 1142 1143 1144 1145

		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 已提交
1146 1147
			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 已提交
1148
		}
E
Erich Gamma 已提交
1149

1150
		if (encodingSupport instanceof UntitledEditorInput) {
A
Alex Dima 已提交
1151
			pickActionPromise = TPromise.as(saveWithEncodingPick);
A
Alex Dima 已提交
1152
		} else if (!isWritableBaseEditor(activeEditor)) {
A
Alex Dima 已提交
1153
			pickActionPromise = TPromise.as(reopenWithEncodingPick);
E
Erich Gamma 已提交
1154
		} else {
B
Benjamin Pasero 已提交
1155
			pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
E
Erich Gamma 已提交
1156 1157
		}

1158
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1159
			if (!action) {
1160
				return void 0;
E
Erich Gamma 已提交
1161 1162
			}

1163
			const resource = toResource(activeEditor.input, { supportSideBySide: true });
1164

1165
			return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
1166
				.then(() => {
1167 1168
					if (!resource || !this.fileService.canHandleResource(resource)) {
						return TPromise.as(null); // encoding detection only possible for resources the file service can handle
1169 1170
					}

1171 1172
					return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
				})
B
Benjamin Pasero 已提交
1173
				.then((guessedEncoding: string) => {
1174
					const isReopenWithEncoding = (action === reopenWithEncodingPick);
1175

1176
					const configuredEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding');
1177

1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
					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 已提交
1193 1194 1195 1196
							if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
								return false; // do not show encoding if it is the guessed encoding that does not match the configured
							}

1197 1198 1199 1200 1201 1202 1203 1204 1205
							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;
							}

1206
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
1207 1208
						});

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

1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
					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
							}
1225
						}
1226
					});
E
Erich Gamma 已提交
1227 1228 1229
				});
		});
	}
J
Johannes Rieken 已提交
1230
}
1231

1232 1233 1234 1235
class ScreenReaderDetectedExplanation extends Themable {
	private container: HTMLElement;
	private hrElement: HTMLHRElement;
	private _visible: boolean;
1236 1237

	constructor(
1238
		@IThemeService themeService: IThemeService,
1239
		@IContextViewService private readonly contextViewService: IContextViewService,
1240
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
1241
	) {
1242 1243
		super(themeService);
	}
1244

1245 1246 1247
	public get visible(): boolean {
		return this._visible;
	}
1248

1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278
	protected updateStyles(): void {
		if (this.container) {
			const background = this.getColor(editorWidgetBackground);
			this.container.style.backgroundColor = background ? background.toString() : null;

			const widgetShadowColor = this.getColor(widgetShadow);
			this.container.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;

			const contrastBorderColor = this.getColor(contrastBorder);
			this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null;

			const foregroundColor = this.getColor(foreground);
			this.hrElement.style.backgroundColor = foregroundColor ? foregroundColor.toString() : null;
		}
	}

	public show(anchorElement: HTMLElement): void {
		this._visible = true;

		this.contextViewService.showContextView({
			getAnchor: () => {
				const res = getDomNodePagePosition(anchorElement);

				return {
					x: res.left,
					y: res.top - 9, /* above the status bar */
					width: res.width,
					height: res.height
				} as IAnchor;
			},
1279 1280 1281
			render: (container) => {
				return this.renderContents(container);
			},
1282
			onDOMEvent: (e, activeElement) => { },
1283
			onHide: () => {
1284
				this._visible = false;
1285 1286 1287 1288
			}
		});
	}

1289 1290
	public hide(): void {
		this.contextViewService.hideContextView();
1291 1292
	}

1293 1294 1295 1296
	protected renderContents(parent: HTMLElement): IDisposable {
		const toDispose: IDisposable[] = [];

		this.container = $('div.screen-reader-detected-explanation', {
1297 1298 1299
			'aria-hidden': 'true'
		});

1300
		const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
1301
		this.container.appendChild(title);
1302 1303

		const closeBtn = $('div.cancel');
1304
		toDispose.push(addDisposableListener(closeBtn, 'click', () => {
1305 1306
			this.contextViewService.hideContextView();
		}));
1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326
		toDispose.push(addDisposableListener(closeBtn, 'mouseover', () => {
			const theme = this.themeService.getTheme();
			let darkenFactor: number;
			switch (theme.type) {
				case 'light':
					darkenFactor = 0.1;
					break;
				case 'dark':
					darkenFactor = 0.2;
					break;
			}

			if (darkenFactor) {
				closeBtn.style.backgroundColor = this.getColor(editorWidgetBackground, (color, theme) => darken(color, darkenFactor)(theme));
			}
		}));
		toDispose.push(addDisposableListener(closeBtn, 'mouseout', () => {
			closeBtn.style.backgroundColor = null;
		}));
		this.container.appendChild(closeBtn);
1327 1328

		const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"));
1329
		this.container.appendChild(question);
1330

1331
		const buttonContainer = $('div.buttons');
1332
		this.container.appendChild(buttonContainer);
1333 1334 1335

		const yesBtn = new Button(buttonContainer);
		yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
1336 1337
		toDispose.push(attachButtonStyler(yesBtn, this.themeService));
		toDispose.push(yesBtn.onDidClick(e => {
1338
			this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
1339 1340 1341
			this.contextViewService.hideContextView();
		}));

1342 1343
		const noBtn = new Button(buttonContainer);
		noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
1344 1345
		toDispose.push(attachButtonStyler(noBtn, this.themeService));
		toDispose.push(noBtn.onDidClick(e => {
1346
			this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
1347 1348 1349 1350 1351
			this.contextViewService.hideContextView();
		}));

		const clear = $('div');
		clear.style.clear = 'both';
1352
		this.container.appendChild(clear);
1353 1354

		const br = $('br');
1355
		this.container.appendChild(br);
1356

1357 1358
		this.hrElement = $('hr');
		this.container.appendChild(this.hrElement);
1359 1360

		const explanation1 = $('p.body1', {}, nls.localize('screenReaderDetectedExplanation.body1', "VS Code is now optimized for usage with a screen reader."));
1361
		this.container.appendChild(explanation1);
1362

1363
		const explanation2 = $('p.body2', {}, nls.localize('screenReaderDetectedExplanation.body2', "Some editor features will have different behaviour: e.g. word wrapping, folding, etc."));
1364
		this.container.appendChild(explanation2);
1365

1366
		parent.appendChild(this.container);
1367

1368
		this.updateStyles();
1369 1370

		return {
1371
			dispose: () => dispose(toDispose)
1372 1373 1374
		};
	}
}