editorStatus.ts 49.2 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 * as nls from '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 * as strings from 'vs/base/common/strings';
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
E
Erich Gamma 已提交
15
import uri from 'vs/base/common/uri';
16
import * as errors from '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, IEditor as IBaseEditor, IEditorInput } 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 { IEditorService } 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, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
37 38 39
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
40
import { Range } from 'vs/editor/common/core/range';
J
Johannes Rieken 已提交
41 42
import { Selection } from 'vs/editor/common/core/selection';
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';
46
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
47
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
48
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
49 50
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
51 52
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
53 54
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
B
Benjamin Pasero 已提交
55
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
56 57
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
58 59
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { Themable } from 'vs/workbench/common/theme';
S
Sandeep Somavarapu 已提交
60
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
B
Benjamin Pasero 已提交
61

B
Benjamin Pasero 已提交
62 63 64
class SideBySideEditorEncodingSupport implements IEncodingSupport {
	constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }

B
Benjamin Pasero 已提交
65
	getEncoding(): string {
B
Benjamin Pasero 已提交
66
		return this.master.getEncoding(); // always report from modified (right hand) side
67 68
	}

B
Benjamin Pasero 已提交
69
	setEncoding(encoding: string, mode: EncodingMode): void {
B
Benjamin Pasero 已提交
70 71 72 73 74 75 76
		[this.master, this.details].forEach(s => s.setEncoding(encoding, mode));
	}
}

function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport {

	// Untitled Editor
77 78 79 80
	if (input instanceof UntitledEditorInput) {
		return input;
	}

B
Benjamin Pasero 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93
	// Side by Side (diff) Editor
	if (input instanceof SideBySideEditorInput) {
		const masterEncodingSupport = toEditorWithEncodingSupport(input.master);
		const detailsEncodingSupport = toEditorWithEncodingSupport(input.details);

		if (masterEncodingSupport && detailsEncodingSupport) {
			return new SideBySideEditorEncodingSupport(masterEncodingSupport, detailsEncodingSupport);
		}

		return masterEncodingSupport;
	}

	// File or Resource Editor
94 95 96 97 98
	let encodingSupport = input as IFileEditorInput;
	if (types.areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
		return encodingSupport;
	}

B
Benjamin Pasero 已提交
99
	// Unsupported for any other editor
100
	return null;
101 102
}

E
Erich Gamma 已提交
103
interface IEditorSelectionStatus {
104
	selections?: Selection[];
E
Erich Gamma 已提交
105 106 107
	charactersSelected?: number;
}

108
class StateChange {
A
Alex Dima 已提交
109
	_stateChangeBrand: void;
110

I
isidor 已提交
111
	indentation: boolean;
112 113 114 115
	selectionStatus: boolean;
	mode: boolean;
	encoding: boolean;
	EOL: boolean;
E
Erich Gamma 已提交
116
	tabFocusMode: boolean;
117
	screenReaderMode: boolean;
118
	metadata: boolean;
119 120 121 122 123 124 125 126

	constructor() {
		this.indentation = false;
		this.selectionStatus = false;
		this.mode = false;
		this.encoding = false;
		this.EOL = false;
		this.tabFocusMode = false;
127
		this.screenReaderMode = false;
128
		this.metadata = false;
129 130
	}

B
Benjamin Pasero 已提交
131
	combine(other: StateChange) {
132 133 134 135 136 137
		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;
138
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
139
		this.metadata = this.metadata || other.metadata;
140
	}
E
Erich Gamma 已提交
141 142
}

143 144 145 146 147
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
148
	indentation?: string;
149
	tabFocusMode?: boolean;
150
	screenReaderMode?: boolean;
151
	metadata?: string;
152 153 154 155
}

class State {
	private _selectionStatus: string;
B
Benjamin Pasero 已提交
156
	get selectionStatus(): string { return this._selectionStatus; }
157 158

	private _mode: string;
B
Benjamin Pasero 已提交
159
	get mode(): string { return this._mode; }
160 161

	private _encoding: string;
B
Benjamin Pasero 已提交
162
	get encoding(): string { return this._encoding; }
163 164

	private _EOL: string;
B
Benjamin Pasero 已提交
165
	get EOL(): string { return this._EOL; }
166

I
isidor 已提交
167
	private _indentation: string;
B
Benjamin Pasero 已提交
168
	get indentation(): string { return this._indentation; }
I
isidor 已提交
169

170
	private _tabFocusMode: boolean;
B
Benjamin Pasero 已提交
171
	get tabFocusMode(): boolean { return this._tabFocusMode; }
172

173
	private _screenReaderMode: boolean;
B
Benjamin Pasero 已提交
174
	get screenReaderMode(): boolean { return this._screenReaderMode; }
175

176
	private _metadata: string;
B
Benjamin Pasero 已提交
177
	get metadata(): string { return this._metadata; }
178

179 180 181 182 183 184
	constructor() {
		this._selectionStatus = null;
		this._mode = null;
		this._encoding = null;
		this._EOL = null;
		this._tabFocusMode = false;
185
		this._screenReaderMode = false;
186
		this._metadata = null;
187 188
	}

B
Benjamin Pasero 已提交
189
	update(update: StateDelta): StateChange {
B
Benjamin Pasero 已提交
190
		const e = new StateChange();
191 192 193 194 195 196 197 198 199
		let somethingChanged = false;

		if (typeof update.selectionStatus !== 'undefined') {
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
				somethingChanged = true;
				e.selectionStatus = true;
			}
		}
I
isidor 已提交
200
		if (typeof update.indentation !== 'undefined') {
201 202
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
I
isidor 已提交
203 204 205 206
				somethingChanged = true;
				e.indentation = true;
			}
		}
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
		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;
			}
		}
235 236 237 238 239 240 241
		if (typeof update.screenReaderMode !== 'undefined') {
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
				somethingChanged = true;
				e.screenReaderMode = true;
			}
		}
242 243 244 245 246 247 248
		if (typeof update.metadata !== 'undefined') {
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
				somethingChanged = true;
				e.metadata = true;
			}
		}
249 250 251 252 253 254 255 256

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

257 258 259 260 261 262
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 已提交
263
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
264
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
A
Alex Dima 已提交
265
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
E
Erich Gamma 已提交
266

267
function setDisplay(el: HTMLElement, desiredValue: string): void {
A
Alex Dima 已提交
268 269 270 271
	if (el.style.display !== desiredValue) {
		el.style.display = desiredValue;
	}
}
272
function show(el: HTMLElement): void {
273
	setDisplay(el, '');
A
Alex Dima 已提交
274
}
275
function hide(el: HTMLElement): void {
276
	setDisplay(el, 'none');
A
Alex Dima 已提交
277 278
}

279
export class EditorStatus implements IStatusbarItem {
280
	private state: State;
281 282
	private element: HTMLElement;
	private tabFocusModeElement: HTMLElement;
283
	private screenRedearModeElement: HTMLElement;
I
isidor 已提交
284
	private indentationElement: HTMLElement;
285 286 287 288
	private selectionElement: HTMLElement;
	private encodingElement: HTMLElement;
	private eolElement: HTMLElement;
	private modeElement: HTMLElement;
289
	private metadataElement: HTMLElement;
E
Erich Gamma 已提交
290
	private toDispose: IDisposable[];
291
	private activeEditorListeners: IDisposable[];
292 293
	private delayedRender: IDisposable;
	private toRender: StateChange;
294
	private screenReaderExplanation: ScreenReaderDetectedExplanation;
E
Erich Gamma 已提交
295

296
	constructor(
297
		@IEditorService private editorService: IEditorService,
298 299
		@IQuickOpenService private quickOpenService: IQuickOpenService,
		@IInstantiationService private instantiationService: IInstantiationService,
300
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
301
		@IModeService private modeService: IModeService,
302 303
		@ITextFileService private textFileService: ITextFileService,
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
304 305
	) {
		this.toDispose = [];
306
		this.activeEditorListeners = [];
307
		this.state = new State();
E
Erich Gamma 已提交
308 309
	}

B
Benjamin Pasero 已提交
310
	render(container: HTMLElement): IDisposable {
311 312
		this.element = append(container, $('.editor-statusbar-item'));

313
		this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
314 315 316
		this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode");
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;
317
		hide(this.tabFocusModeElement);
318

319 320
		this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info'));
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
321
		this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle;
322
		this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();
323 324
		hide(this.screenRedearModeElement);

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

330
		this.indentationElement = append(this.element, $('a.editor-status-indentation'));
331
		this.indentationElement.title = nls.localize('selectIndentation', "Select Indentation");
332 333 334
		this.indentationElement.onclick = () => this.onIndentationClick();
		hide(this.indentationElement);

335 336 337
		this.encodingElement = append(this.element, $('a.editor-status-encoding'));
		this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding");
		this.encodingElement.onclick = () => this.onEncodingClick();
338
		hide(this.encodingElement);
339 340 341 342

		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();
343
		hide(this.eolElement);
344 345 346 347

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

350 351 352 353
		this.metadataElement = append(this.element, $('span.editor-status-metadata'));
		this.metadataElement.title = nls.localize('fileInfo', "File Information");
		hide(this.metadataElement);

354 355 356
		this.delayedRender = null;
		this.toRender = null;

357
		this.toDispose.push(
358 359 360 361 362 363 364 365
			{
				dispose: () => {
					if (this.delayedRender) {
						this.delayedRender.dispose();
						this.delayedRender = null;
					}
				}
			},
366
			this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
367 368
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
369
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
370
		);
E
Erich Gamma 已提交
371

J
Joao Moreno 已提交
372
		return combinedDisposable(this.toDispose);
373 374
	}

375
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
376
		const changed = this.state.update(update);
377 378 379
		if (!changed) {
			// Nothing really changed
			return;
E
Erich Gamma 已提交
380 381
		}

382 383 384 385
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
386
				const toRender = this.toRender;
387 388 389 390 391 392 393 394
				this.toRender = null;
				this._renderNow(toRender);
			});
		} else {
			this.toRender.combine(changed);
		}
	}

395
	private _renderNow(changed: StateChange): void {
396 397 398 399 400 401
		if (changed.tabFocusMode) {
			if (this.state.tabFocusMode && this.state.tabFocusMode === true) {
				show(this.tabFocusModeElement);
			} else {
				hide(this.tabFocusModeElement);
			}
E
Erich Gamma 已提交
402 403
		}

404 405 406 407 408 409 410 411
		if (changed.screenReaderMode) {
			if (this.state.screenReaderMode && this.state.screenReaderMode === true) {
				show(this.screenRedearModeElement);
			} else {
				hide(this.screenRedearModeElement);
			}
		}

I
isidor 已提交
412 413 414 415 416 417 418 419 420
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

421
		if (changed.selectionStatus) {
422
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
423 424 425 426 427
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
428 429
		}

430 431 432 433 434 435 436
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
437 438
		}

439 440 441 442 443 444 445
		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 已提交
446 447
		}

448 449 450 451 452 453 454 455
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
456 457 458 459 460 461 462 463 464

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

467
	private getSelectionLabel(info: IEditorSelectionStatus): string {
E
Erich Gamma 已提交
468 469 470 471 472 473
		if (!info || !info.selections) {
			return null;
		}

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

			return strings.format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
478
		}
B
Benjamin Pasero 已提交
479 480 481 482 483 484 485 486 487 488

		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 已提交
489 490
	}

491
	private onModeClick(): void {
B
Benjamin Pasero 已提交
492
		const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
493 494 495 496 497

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

I
isidor 已提交
498 499 500 501 502 503
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
		action.run().done(null, errors.onUnexpectedError);
		action.dispose();
	}

504
	private onScreenReaderModeClick(): void {
505 506 507 508 509 510 511 512 513 514 515 516
		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();
		}
517 518
	}

519 520 521 522 523
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
B
Benjamin Pasero 已提交
524
		const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
525 526 527 528 529 530

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

	private onEncodingClick(): void {
B
Benjamin Pasero 已提交
531
		const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
532 533 534 535 536 537

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

	private onTabFocusModeClick(): void {
538
		TabFocus.setTabFocusMode(false);
539 540
	}

541 542
	private updateStatusBar(): void {
		const activeControl = this.editorService.activeControl;
B
Benjamin Pasero 已提交
543
		const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : void 0;
544 545

		// Update all states
546 547 548 549 550 551 552
		this.onScreenReaderModeChange(activeCodeEditor);
		this.onSelectionChange(activeCodeEditor);
		this.onModeChange(activeCodeEditor);
		this.onEOLChange(activeCodeEditor);
		this.onEncodingChange(activeControl);
		this.onIndentationChange(activeCodeEditor);
		this.onMetadataChange(activeControl);
E
Erich Gamma 已提交
553

554 555 556 557
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
558
		if (activeCodeEditor) {
559

560
			// Hook Listener for Configuration changes
561
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
562
				if (event.accessibilitySupport) {
563
					this.onScreenReaderModeChange(activeCodeEditor);
564 565 566
				}
			}));

567
			// Hook Listener for Selection changes
568 569
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(activeCodeEditor);
570 571 572
			}));

			// Hook Listener for mode changes
573 574
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
				this.onModeChange(activeCodeEditor);
575 576 577
			}));

			// Hook Listener for content changes
578 579
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => {
				this.onEOLChange(activeCodeEditor);
580 581 582 583 584 585 586 587

				let selections = activeCodeEditor.getSelections();
				for (let i = 0; i < e.changes.length; i++) {
					if (selections.some(selection => Range.areIntersecting(selection, e.changes[i].range))) {
						this.onSelectionChange(activeCodeEditor);
						break;
					}
				}
588 589 590
			}));

			// Hook Listener for content options changes
591 592
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
				this.onIndentationChange(activeCodeEditor);
593
			}));
E
Erich Gamma 已提交
594
		}
595 596

		// Handle binary editors
597
		else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
598
			const binaryEditors: BaseBinaryResourceEditor[] = [];
599 600
			if (activeControl instanceof BinaryResourceDiffEditor) {
				const details = activeControl.getDetailsEditor();
601 602 603 604
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

605
				const master = activeControl.getMasterEditor();
606 607 608
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
609
			} else {
610
				binaryEditors.push(activeControl);
611 612 613 614
			}

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
615
					this.onMetadataChange(activeControl);
616 617
				}));
			});
618
		}
619
	}
E
Erich Gamma 已提交
620

621
	private onModeChange(editorWidget: ICodeEditor): void {
622
		let info: StateDelta = { mode: null };
E
Erich Gamma 已提交
623 624

		// We only support text based editors
625
		if (editorWidget) {
A
Alex Dima 已提交
626
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
627 628
			if (textModel) {
				// Compute mode
A
Alex Dima 已提交
629
				const modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
630
				info = { mode: this.modeService.getLanguageName(modeId) };
E
Erich Gamma 已提交
631 632 633 634 635 636
			}
		}

		this.updateState(info);
	}

637
	private onIndentationChange(editorWidget: ICodeEditor): void {
638
		const update: StateDelta = { indentation: null };
639

640
		if (editorWidget) {
A
Alex Dima 已提交
641
			const model = editorWidget.getModel();
642 643 644 645 646 647 648
			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)
				);
649
			}
I
isidor 已提交
650
		}
651

I
isidor 已提交
652 653 654
		this.updateState(update);
	}

655 656 657 658 659 660 661 662 663 664
	private onMetadataChange(editor: IBaseEditor): void {
		const update: StateDelta = { metadata: null };

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

		this.updateState(update);
	}

665 666
	private _promptedScreenReader: boolean = false;

667
	private onScreenReaderModeChange(editorWidget: ICodeEditor): void {
668 669 670 671
		let screenReaderMode = false;

		// We only support text based editors
		if (editorWidget) {
672 673
			const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled);
			if (screenReaderDetected) {
674
				const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
675 676 677 678 679 680 681 682 683 684
				if (screenReaderConfiguration === 'auto') {
					// show explanation
					if (!this._promptedScreenReader) {
						this._promptedScreenReader = true;
						setTimeout(() => {
							this.onScreenReaderModeClick();
						}, 100);
					}
				}
			}
685 686 687 688

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

689 690
		if (screenReaderMode === false && this.screenReaderExplanation && this.screenReaderExplanation.visible) {
			this.screenReaderExplanation.hide();
691 692
		}

693 694 695
		this.updateState({ screenReaderMode: screenReaderMode });
	}

696
	private onSelectionChange(editorWidget: ICodeEditor): void {
B
Benjamin Pasero 已提交
697
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
698 699

		// We only support text based editors
700
		if (editorWidget) {
E
Erich Gamma 已提交
701 702 703 704 705 706

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
707
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
708
			if (textModel) {
709
				info.selections.forEach(selection => {
E
Erich Gamma 已提交
710 711 712 713 714 715
					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 已提交
716
				const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition());
E
Erich Gamma 已提交
717

718 719 720 721 722 723 724
				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 已提交
725 726 727 728 729

				info.selections[0] = selectionClone;
			}
		}

730
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
731 732
	}

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

A
Alex Dima 已提交
736 737
		if (editorWidget && !editorWidget.getConfiguration().readOnly) {
			const codeEditorModel = editorWidget.getModel();
I
isidor 已提交
738 739 740
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
741 742 743 744 745
		}

		this.updateState(info);
	}

746
	private onEncodingChange(e?: IBaseEditor): void {
E
Erich Gamma 已提交
747 748 749 750
		if (e && !this.isActiveEditor(e)) {
			return;
		}

B
Benjamin Pasero 已提交
751
		const info: StateDelta = { encoding: null };
E
Erich Gamma 已提交
752 753

		// We only support text based editors
754
		if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
755 756
			const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
			if (encodingSupport) {
B
Benjamin Pasero 已提交
757 758
				const rawEncoding = encodingSupport.getEncoding();
				const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
759 760 761 762 763 764 765 766 767 768 769
				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);
	}

770
	private onResourceEncodingChange(resource: uri): void {
771 772 773
		const activeControl = this.editorService.activeControl;
		if (activeControl) {
			const activeResource = toResource(activeControl.input, { supportSideBySide: true });
B
Benjamin Pasero 已提交
774
			if (activeResource && activeResource.toString() === resource.toString()) {
775
				return this.onEncodingChange(<IBaseEditor>activeControl); // only update if the encoding changed for the active resource
776 777
			}
		}
E
Erich Gamma 已提交
778 779
	}

780
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
781
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
782 783 784 785

		this.updateState(info);
	}

786 787
	private isActiveEditor(control: IBaseEditor): boolean {
		const activeControl = this.editorService.activeControl;
E
Erich Gamma 已提交
788

B
Benjamin Pasero 已提交
789
		return activeControl && activeControl === control;
E
Erich Gamma 已提交
790 791 792
	}
}

793
function isWritableCodeEditor(codeEditor: ICodeEditor): boolean {
A
Alex Dima 已提交
794
	if (!codeEditor) {
S
Sandeep Somavarapu 已提交
795 796
		return false;
	}
A
Alex Dima 已提交
797 798 799
	const config = codeEditor.getConfiguration();
	return (!config.readOnly);
}
S
Sandeep Somavarapu 已提交
800

A
Alex Dima 已提交
801
function isWritableBaseEditor(e: IBaseEditor): boolean {
B
Benjamin Pasero 已提交
802
	return e && isWritableCodeEditor(getCodeEditor(e.getControl()));
E
Erich Gamma 已提交
803 804
}

805 806
export class ShowLanguageExtensionsAction extends Action {

807
	static readonly ID = 'workbench.action.showLanguageExtensions';
808 809

	constructor(
810 811
		private fileExtension: string,
		@ICommandService private commandService: ICommandService,
812 813
		@IExtensionGalleryService galleryService: IExtensionGalleryService
	) {
814 815
		super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));

816 817 818 819
		this.enabled = galleryService.isEnabled();
	}

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

E
Erich Gamma 已提交
824 825
export class ChangeModeAction extends Action {

B
Benjamin Pasero 已提交
826 827
	static readonly ID = 'workbench.action.editor.changeLanguageMode';
	static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
E
Erich Gamma 已提交
828 829 830 831 832

	constructor(
		actionId: string,
		actionLabel: string,
		@IModeService private modeService: IModeService,
833
		@IModelService private modelService: IModelService,
834
		@IEditorService private editorService: IEditorService,
835
		@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
836
		@IQuickOpenService private quickOpenService: IQuickOpenService,
837 838
		@IPreferencesService private preferencesService: IPreferencesService,
		@IInstantiationService private instantiationService: IInstantiationService,
839
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService
E
Erich Gamma 已提交
840 841 842 843
	) {
		super(actionId, actionLabel);
	}

B
Benjamin Pasero 已提交
844
	run(): TPromise<any> {
845 846
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
E
Erich Gamma 已提交
847 848 849
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

850
		const textModel = activeTextEditorWidget.getModel();
B
Benjamin Pasero 已提交
851
		const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
B
Benjamin Pasero 已提交
852 853

		let hasLanguageSupport = !!resource;
854
		if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
B
Benjamin Pasero 已提交
855
			hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
856
		}
E
Erich Gamma 已提交
857 858 859

		// Compute mode
		let currentModeId: string;
860
		let modeId: string;
861
		if (textModel) {
862
			modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
863
			currentModeId = this.modeService.getLanguageName(modeId);
E
Erich Gamma 已提交
864 865 866
		}

		// All languages are valid picks
867
		const languages = this.modeService.getRegisteredLanguageNames();
B
Benjamin Pasero 已提交
868
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
869 870 871 872 873 874
			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()));
			}
875

876 877 878 879 880 881 882 883 884 885 886 887 888
			// 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>{
889
				label: lang,
890
				resource: fakeResource,
B
Benjamin Pasero 已提交
891
				description
E
Erich Gamma 已提交
892 893
			};
		});
894

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

899
		// Offer action to configure via settings
900
		let configureModeAssociations: IPickOpenEntry;
901
		let configureModeSettings: IPickOpenEntry;
902
		let galleryAction: Action;
B
Benjamin Pasero 已提交
903
		if (hasLanguageSupport) {
904
			const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
S
Sandeep Somavarapu 已提交
905 906 907 908 909 910

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

911 912
			configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
			picks.unshift(configureModeSettings);
913
			configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
914 915
			picks.unshift(configureModeAssociations);
		}
E
Erich Gamma 已提交
916

917
		// Offer to "Auto Detect"
B
Benjamin Pasero 已提交
918
		const autoDetectMode: IPickOpenEntry = {
919 920
			label: nls.localize('autoDetect', "Auto Detect")
		};
921

B
Benjamin Pasero 已提交
922
		if (hasLanguageSupport) {
923 924
			picks.unshift(autoDetectMode);
		}
925

926
		return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
927 928 929
			if (!pick) {
				return;
			}
B
Benjamin Pasero 已提交
930

931 932 933 934
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
935

936 937
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
938
				this.configureFileAssociation(resource);
939 940
				return;
			}
941

942 943
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
944
				this.preferencesService.configureSettingsForLanguage(modeId);
945 946 947
				return;
			}

948
			// Change mode for active editor
B
Benjamin Pasero 已提交
949
			const activeEditor = this.editorService.activeEditor;
950
			const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
A
Alex Dima 已提交
951
			const models: ITextModel[] = [];
952 953
			if (isCodeEditor(activeTextEditorWidget)) {
				const codeEditorModel = activeTextEditorWidget.getModel();
954 955
				if (codeEditorModel) {
					models.push(codeEditorModel);
956
				}
957 958
			} else if (isDiffEditor(activeTextEditorWidget)) {
				const diffEditorModel = activeTextEditorWidget.getModel();
959 960 961 962 963 964 965
				if (diffEditorModel) {
					if (diffEditorModel.original) {
						models.push(diffEditorModel.original);
					}
					if (diffEditorModel.modified) {
						models.push(diffEditorModel.modified);
					}
E
Erich Gamma 已提交
966
				}
967
			}
968

969 970 971
			// Find mode
			let mode: TPromise<IMode>;
			if (pick === autoDetectMode) {
B
Benjamin Pasero 已提交
972
				mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
973 974
			} else {
				mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
E
Erich Gamma 已提交
975
			}
976 977 978 979 980

			// Change mode
			models.forEach(textModel => {
				this.modelService.setMode(textModel, mode);
			});
E
Erich Gamma 已提交
981 982
		});
	}
983 984

	private configureFileAssociation(resource: uri): void {
985 986 987 988
		const extension = paths.extname(resource.fsPath);
		const basename = paths.basename(resource.fsPath);
		const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);

989 990
		const languages = this.modeService.getRegisteredLanguageNames();
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
991 992
			const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());

993
			return <IPickOpenEntry>{
994 995 996
				id,
				label: lang,
				description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0
997 998 999 1000 1001 1002
			};
		});

		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) {
1003
					const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
1004 1005 1006 1007 1008 1009 1010 1011

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

1012 1013 1014 1015 1016 1017 1018
					// 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 已提交
1019
					let currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user);
1020 1021 1022
					if (!currentAssociations) {
						currentAssociations = Object.create(null);
					}
1023

1024 1025
					currentAssociations[associationKey] = language.id;

1026
					this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
1027 1028 1029 1030
				}
			});
		});
	}
E
Erich Gamma 已提交
1031 1032 1033 1034 1035 1036
}

export interface IChangeEOLEntry extends IPickOpenEntry {
	eol: EndOfLineSequence;
}

I
isidor 已提交
1037
class ChangeIndentationAction extends Action {
I
isidor 已提交
1038

B
Benjamin Pasero 已提交
1039 1040
	static readonly ID = 'workbench.action.editor.changeIndentation';
	static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1041 1042 1043 1044

	constructor(
		actionId: string,
		actionLabel: string,
1045
		@IEditorService private editorService: IEditorService,
I
isidor 已提交
1046 1047 1048 1049 1050
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

B
Benjamin Pasero 已提交
1051
	run(): TPromise<any> {
1052 1053
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
I
isidor 已提交
1054 1055
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
B
Benjamin Pasero 已提交
1056

1057
		if (!isWritableCodeEditor(activeTextEditorWidget)) {
I
isidor 已提交
1058 1059
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}
1060

B
Benjamin Pasero 已提交
1061
		const picks = [
1062 1063 1064 1065 1066 1067
			activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
			activeTextEditorWidget.getAction(IndentUsingTabs.ID),
			activeTextEditorWidget.getAction(DetectIndentation.ID),
			activeTextEditorWidget.getAction(IndentationToSpacesAction.ID),
			activeTextEditorWidget.getAction(IndentationToTabsAction.ID),
			activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID)
A
Alex Dima 已提交
1068
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
1069 1070
			return {
				id: a.id,
B
Benjamin Pasero 已提交
1071
				label: a.label,
A
Alex Dima 已提交
1072
				detail: (language === LANGUAGE_DEFAULT) ? null : a.alias,
1073
				run: () => {
1074
					activeTextEditorWidget.focus();
1075 1076
					a.run();
				}
B
Benjamin Pasero 已提交
1077 1078 1079
			};
		});

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

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

E
Erich Gamma 已提交
1087 1088
export class ChangeEOLAction extends Action {

B
Benjamin Pasero 已提交
1089 1090
	static readonly ID = 'workbench.action.editor.changeEOL';
	static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
E
Erich Gamma 已提交
1091 1092 1093 1094

	constructor(
		actionId: string,
		actionLabel: string,
1095
		@IEditorService private editorService: IEditorService,
E
Erich Gamma 已提交
1096 1097 1098 1099 1100
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

B
Benjamin Pasero 已提交
1101
	run(): TPromise<any> {
1102 1103
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
E
Erich Gamma 已提交
1104 1105 1106
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

1107
		if (!isWritableCodeEditor(activeTextEditorWidget)) {
E
Erich Gamma 已提交
1108 1109 1110
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}

1111
		const textModel = activeTextEditorWidget.getModel();
E
Erich Gamma 已提交
1112

B
Benjamin Pasero 已提交
1113
		const EOLOptions: IChangeEOLEntry[] = [
1114 1115
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1116 1117
		];

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

1120
		return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
E
Erich Gamma 已提交
1121
			if (eol) {
1122
				const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
1123 1124
				if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) {
					const textModel = activeCodeEditor.getModel();
A
Alex Dima 已提交
1125
					textModel.pushEOL(eol.eol);
E
Erich Gamma 已提交
1126 1127 1128 1129 1130 1131 1132 1133
				}
			}
		});
	}
}

export class ChangeEncodingAction extends Action {

B
Benjamin Pasero 已提交
1134 1135
	static readonly ID = 'workbench.action.editor.changeEncoding';
	static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
E
Erich Gamma 已提交
1136 1137 1138 1139

	constructor(
		actionId: string,
		actionLabel: string,
1140
		@IEditorService private editorService: IEditorService,
E
Erich Gamma 已提交
1141
		@IQuickOpenService private quickOpenService: IQuickOpenService,
1142
		@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
1143
		@IFileService private fileService: IFileService
E
Erich Gamma 已提交
1144 1145 1146 1147
	) {
		super(actionId, actionLabel);
	}

B
Benjamin Pasero 已提交
1148
	run(): TPromise<any> {
1149
		if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
E
Erich Gamma 已提交
1150 1151 1152
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

B
Benjamin Pasero 已提交
1153
		let activeControl = this.editorService.activeControl;
1154
		let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
1155
		if (!encodingSupport) {
E
Erich Gamma 已提交
1156 1157 1158 1159
			return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
		}

		let pickActionPromise: TPromise<IPickOpenEntry>;
B
Benjamin Pasero 已提交
1160 1161 1162 1163 1164 1165 1166

		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 已提交
1167 1168
			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 已提交
1169
		}
E
Erich Gamma 已提交
1170

1171
		if (encodingSupport instanceof UntitledEditorInput) {
A
Alex Dima 已提交
1172
			pickActionPromise = TPromise.as(saveWithEncodingPick);
1173
		} else if (!isWritableBaseEditor(activeControl)) {
A
Alex Dima 已提交
1174
			pickActionPromise = TPromise.as(reopenWithEncodingPick);
E
Erich Gamma 已提交
1175
		} else {
B
Benjamin Pasero 已提交
1176
			pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
E
Erich Gamma 已提交
1177 1178
		}

1179
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1180
			if (!action) {
1181
				return void 0;
E
Erich Gamma 已提交
1182 1183
			}

1184
			const resource = toResource(activeControl.input, { supportSideBySide: true });
1185

1186
			return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
1187
				.then(() => {
1188 1189
					if (!resource || !this.fileService.canHandleResource(resource)) {
						return TPromise.as(null); // encoding detection only possible for resources the file service can handle
1190 1191
					}

1192 1193
					return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
				})
B
Benjamin Pasero 已提交
1194
				.then((guessedEncoding: string) => {
1195
					const isReopenWithEncoding = (action === reopenWithEncodingPick);
1196

1197
					const configuredEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding');
1198

1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
					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 已提交
1214 1215 1216 1217
							if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
								return false; // do not show encoding if it is the guessed encoding that does not match the configured
							}

1218 1219 1220 1221 1222 1223 1224 1225 1226
							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;
							}

1227
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
1228 1229
						});

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

1236 1237 1238 1239 1240
					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) {
1241 1242
							activeControl = this.editorService.activeControl;
							encodingSupport = toEditorWithEncodingSupport(activeControl.input);
1243 1244 1245
							if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
								encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
							}
1246
						}
1247
					});
E
Erich Gamma 已提交
1248 1249 1250
				});
		});
	}
J
Johannes Rieken 已提交
1251
}
1252

1253 1254 1255 1256
class ScreenReaderDetectedExplanation extends Themable {
	private container: HTMLElement;
	private hrElement: HTMLHRElement;
	private _visible: boolean;
1257 1258

	constructor(
1259
		@IThemeService themeService: IThemeService,
1260
		@IContextViewService private readonly contextViewService: IContextViewService,
1261
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
1262
	) {
1263 1264
		super(themeService);
	}
1265

B
Benjamin Pasero 已提交
1266
	get visible(): boolean {
1267 1268
		return this._visible;
	}
1269

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285
	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;
		}
	}

B
Benjamin Pasero 已提交
1286
	show(anchorElement: HTMLElement): void {
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
		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;
			},
1300 1301 1302
			render: (container) => {
				return this.renderContents(container);
			},
1303
			onDOMEvent: (e, activeElement) => { },
1304
			onHide: () => {
1305
				this._visible = false;
1306 1307 1308 1309
			}
		});
	}

B
Benjamin Pasero 已提交
1310
	hide(): void {
1311
		this.contextViewService.hideContextView();
1312 1313
	}

1314 1315 1316 1317
	protected renderContents(parent: HTMLElement): IDisposable {
		const toDispose: IDisposable[] = [];

		this.container = $('div.screen-reader-detected-explanation', {
1318 1319 1320
			'aria-hidden': 'true'
		});

1321
		const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
1322
		this.container.appendChild(title);
1323 1324

		const closeBtn = $('div.cancel');
1325
		toDispose.push(addDisposableListener(closeBtn, 'click', () => {
1326 1327
			this.contextViewService.hideContextView();
		}));
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
		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);
1348 1349

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

1352
		const buttonContainer = $('div.buttons');
1353
		this.container.appendChild(buttonContainer);
1354 1355 1356

		const yesBtn = new Button(buttonContainer);
		yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
1357 1358
		toDispose.push(attachButtonStyler(yesBtn, this.themeService));
		toDispose.push(yesBtn.onDidClick(e => {
1359
			this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
1360 1361 1362
			this.contextViewService.hideContextView();
		}));

1363 1364
		const noBtn = new Button(buttonContainer);
		noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
1365 1366
		toDispose.push(attachButtonStyler(noBtn, this.themeService));
		toDispose.push(noBtn.onDidClick(e => {
1367
			this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
1368 1369 1370 1371 1372
			this.contextViewService.hideContextView();
		}));

		const clear = $('div');
		clear.style.clear = 'both';
1373
		this.container.appendChild(clear);
1374 1375

		const br = $('br');
1376
		this.container.appendChild(br);
1377

1378 1379
		this.hrElement = $('hr');
		this.container.appendChild(this.hrElement);
1380 1381

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

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

1387
		parent.appendChild(this.container);
1388

1389
		this.updateStyles();
1390 1391

		return {
1392
			dispose: () => dispose(toDispose)
1393 1394 1395
		};
	}
}