editorStatus.ts 49.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 * 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 { INextEditorService } from 'vs/workbench/services/editor/common/nextEditorService';
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 40 41
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 { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
42
import { ICommandService } from 'vs/platform/commands/common/commands';
43
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
44
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
45
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
46
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
47
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
48 49
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
50 51
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
52 53
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { deepClone } from 'vs/base/common/objects';
B
Benjamin Pasero 已提交
54
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
55 56
import { Button } from 'vs/base/browser/ui/button/button';
import { Schemas } from 'vs/base/common/network';
57 58
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { Themable } from 'vs/workbench/common/theme';
S
Sandeep Somavarapu 已提交
59
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
60
import { INextEditorGroupsService } from 'vs/workbench/services/group/common/nextEditorGroupsService';
B
Benjamin Pasero 已提交
61

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

	public getEncoding(): string {
		return this.master.getEncoding(); // always report from modified (right hand) side
67 68
	}

B
Benjamin Pasero 已提交
69 70 71 72 73 74 75 76
	public setEncoding(encoding: string, mode: EncodingMode): void {
		[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
	}

131
	public 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 156 157 158 159 160 161 162 163 164 165 166
}

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

170 171 172
	private _tabFocusMode: boolean;
	public get tabFocusMode(): boolean { return this._tabFocusMode; }

173 174 175
	private _screenReaderMode: boolean;
	public get screenReaderMode(): boolean { return this._screenReaderMode; }

176 177 178
	private _metadata: string;
	public get metadata(): string { return this._metadata; }

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
	}

189
	public 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 {
E
Erich Gamma 已提交
280

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

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

312 313 314
	public render(container: HTMLElement): IDisposable {
		this.element = append(container, $('.editor-statusbar-item'));

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

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

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

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

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

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

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

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

356 357 358
		this.delayedRender = null;
		this.toRender = null;

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

374 375
		this.editorGroupService.whenRestored.then(() => this.updateStatusBar());

J
Joao Moreno 已提交
376
		return combinedDisposable(this.toDispose);
377 378
	}

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

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

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

408 409 410 411 412 413 414 415
		if (changed.screenReaderMode) {
			if (this.state.screenReaderMode && this.state.screenReaderMode === true) {
				show(this.screenRedearModeElement);
			} else {
				hide(this.screenRedearModeElement);
			}
		}

I
isidor 已提交
416 417 418 419 420 421 422 423 424
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

425
		if (changed.selectionStatus) {
426
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
427 428 429 430 431
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
432 433
		}

434 435 436 437 438 439 440
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
441 442
		}

443 444 445 446 447 448 449
		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 已提交
450 451
		}

452 453 454 455 456 457 458 459
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
460 461 462 463 464 465 466 467 468

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

471
	private getSelectionLabel(info: IEditorSelectionStatus): string {
E
Erich Gamma 已提交
472 473 474 475 476 477
		if (!info || !info.selections) {
			return null;
		}

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

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

		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 已提交
493 494
	}

495
	private onModeClick(): void {
B
Benjamin Pasero 已提交
496
		const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
497 498 499 500 501

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

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

508
	private onScreenReaderModeClick(): void {
509 510 511 512 513 514 515 516 517 518 519 520
		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();
		}
521 522
	}

523 524 525 526 527
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
B
Benjamin Pasero 已提交
528
		const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
529 530 531 532 533 534

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

	private onEncodingClick(): void {
B
Benjamin Pasero 已提交
535
		const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
536 537 538 539 540 541

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

	private onTabFocusModeClick(): void {
542
		TabFocus.setTabFocusMode(false);
543 544
	}

545 546
	private updateStatusBar(): void {
		const activeControl = this.editorService.activeControl;
B
Benjamin Pasero 已提交
547
		const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : void 0;
548 549

		// Update all states
550 551 552 553 554 555 556
		this.onScreenReaderModeChange(activeCodeEditor);
		this.onSelectionChange(activeCodeEditor);
		this.onModeChange(activeCodeEditor);
		this.onEOLChange(activeCodeEditor);
		this.onEncodingChange(activeControl);
		this.onIndentationChange(activeCodeEditor);
		this.onMetadataChange(activeControl);
E
Erich Gamma 已提交
557

558 559 560 561
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
562
		if (activeCodeEditor) {
563

564
			// Hook Listener for Configuration changes
565
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
566
				if (event.accessibilitySupport) {
567
					this.onScreenReaderModeChange(activeCodeEditor);
568 569 570
				}
			}));

571
			// Hook Listener for Selection changes
572 573
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(activeCodeEditor);
574 575 576
			}));

			// Hook Listener for mode changes
577 578
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
				this.onModeChange(activeCodeEditor);
579 580 581
			}));

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

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

		// Handle binary editors
593
		else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
594
			const binaryEditors: BaseBinaryResourceEditor[] = [];
595 596
			if (activeControl instanceof BinaryResourceDiffEditor) {
				const details = activeControl.getDetailsEditor();
597 598 599 600
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

601
				const master = activeControl.getMasterEditor();
602 603 604
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
605
			} else {
606
				binaryEditors.push(activeControl);
607 608 609 610
			}

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
611
					this.onMetadataChange(activeControl);
612 613
				}));
			});
614
		}
615
	}
E
Erich Gamma 已提交
616

617
	private onModeChange(editorWidget: ICodeEditor): void {
618
		let info: StateDelta = { mode: null };
E
Erich Gamma 已提交
619 620

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

		this.updateState(info);
	}

633
	private onIndentationChange(editorWidget: ICodeEditor): void {
634
		const update: StateDelta = { indentation: null };
635

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

I
isidor 已提交
648 649 650
		this.updateState(update);
	}

651 652 653 654 655 656 657 658 659 660
	private onMetadataChange(editor: IBaseEditor): void {
		const update: StateDelta = { metadata: null };

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

		this.updateState(update);
	}

661 662
	private _promptedScreenReader: boolean = false;

663
	private onScreenReaderModeChange(editorWidget: ICodeEditor): void {
664 665 666 667
		let screenReaderMode = false;

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

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

685 686
		if (screenReaderMode === false && this.screenReaderExplanation && this.screenReaderExplanation.visible) {
			this.screenReaderExplanation.hide();
687 688
		}

689 690 691
		this.updateState({ screenReaderMode: screenReaderMode });
	}

692
	private onSelectionChange(editorWidget: ICodeEditor): void {
B
Benjamin Pasero 已提交
693
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
694 695

		// We only support text based editors
696
		if (editorWidget) {
E
Erich Gamma 已提交
697 698 699 700 701 702

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

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

714 715 716 717 718 719 720
				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 已提交
721 722 723 724 725

				info.selections[0] = selectionClone;
			}
		}

726
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
727 728
	}

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

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

		this.updateState(info);
	}

742
	private onEncodingChange(e: IBaseEditor): void {
E
Erich Gamma 已提交
743 744 745 746
		if (e && !this.isActiveEditor(e)) {
			return;
		}

B
Benjamin Pasero 已提交
747
		const info: StateDelta = { encoding: null };
E
Erich Gamma 已提交
748 749

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

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

776
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
777
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
778 779 780 781

		this.updateState(info);
	}

782 783
	private isActiveEditor(control: IBaseEditor): boolean {
		const activeControl = this.editorService.activeControl;
E
Erich Gamma 已提交
784

785
		return activeControl && control && activeControl === control;
E
Erich Gamma 已提交
786 787 788
	}
}

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

A
Alex Dima 已提交
797
function isWritableBaseEditor(e: IBaseEditor): boolean {
B
Benjamin Pasero 已提交
798
	return e && isWritableCodeEditor(getCodeEditor(e.getControl()));
E
Erich Gamma 已提交
799 800
}

801 802
export class ShowLanguageExtensionsAction extends Action {

803
	static readonly ID = 'workbench.action.showLanguageExtensions';
804 805

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

812 813 814 815
		this.enabled = galleryService.isEnabled();
	}

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

E
Erich Gamma 已提交
820 821
export class ChangeModeAction extends Action {

M
Matt Bierner 已提交
822 823
	public static readonly ID = 'workbench.action.editor.changeLanguageMode';
	public static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
E
Erich Gamma 已提交
824 825 826 827 828

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

A
Alex Dima 已提交
840
	public run(): TPromise<any> {
B
Benjamin Pasero 已提交
841
		const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
842
		if (!activeCodeEditor) {
E
Erich Gamma 已提交
843 844 845
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

846
		const textModel = activeCodeEditor.getModel();
B
Benjamin Pasero 已提交
847
		const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
B
Benjamin Pasero 已提交
848 849

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

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

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

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

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

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

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

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

913
		// Offer to "Auto Detect"
B
Benjamin Pasero 已提交
914
		const autoDetectMode: IPickOpenEntry = {
915 916
			label: nls.localize('autoDetect', "Auto Detect")
		};
917

B
Benjamin Pasero 已提交
918
		if (hasLanguageSupport) {
919 920
			picks.unshift(autoDetectMode);
		}
921

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

927 928 929 930
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
931

932 933
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
934
				this.configureFileAssociation(resource);
935 936
				return;
			}
937

938 939
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
940
				this.preferencesService.configureSettingsForLanguage(modeId);
941 942 943
				return;
			}

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

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

			// Change mode
			models.forEach(textModel => {
				this.modelService.setMode(textModel, mode);
			});
E
Erich Gamma 已提交
977 978
		});
	}
979 980

	private configureFileAssociation(resource: uri): void {
981 982 983 984
		const extension = paths.extname(resource.fsPath);
		const basename = paths.basename(resource.fsPath);
		const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);

985 986
		const languages = this.modeService.getRegisteredLanguageNames();
		const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
987 988
			const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());

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

		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) {
999
					const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
1000 1001 1002 1003 1004 1005 1006 1007

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

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

1020 1021
					currentAssociations[associationKey] = language.id;

1022
					this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
1023 1024 1025 1026
				}
			});
		});
	}
E
Erich Gamma 已提交
1027 1028 1029 1030 1031 1032
}

export interface IChangeEOLEntry extends IPickOpenEntry {
	eol: EndOfLineSequence;
}

I
isidor 已提交
1033
class ChangeIndentationAction extends Action {
I
isidor 已提交
1034

M
Matt Bierner 已提交
1035 1036
	public static readonly ID = 'workbench.action.editor.changeIndentation';
	public static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1037 1038 1039 1040

	constructor(
		actionId: string,
		actionLabel: string,
1041
		@INextEditorService private editorService: INextEditorService,
I
isidor 已提交
1042 1043 1044 1045 1046 1047
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

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

1053
		if (!isWritableCodeEditor(activeCodeEditor)) {
I
isidor 已提交
1054 1055
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}
1056

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

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

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

E
Erich Gamma 已提交
1083 1084
export class ChangeEOLAction extends Action {

M
Matt Bierner 已提交
1085 1086
	public static readonly ID = 'workbench.action.editor.changeEOL';
	public static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
E
Erich Gamma 已提交
1087 1088 1089 1090

	constructor(
		actionId: string,
		actionLabel: string,
1091
		@INextEditorService private editorService: INextEditorService,
E
Erich Gamma 已提交
1092 1093 1094 1095 1096
		@IQuickOpenService private quickOpenService: IQuickOpenService
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
1097
	public run(): TPromise<any> {
B
Benjamin Pasero 已提交
1098
		const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorControl);
1099
		if (!activeCodeEditor) {
E
Erich Gamma 已提交
1100 1101 1102
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

1103
		if (!isWritableCodeEditor(activeCodeEditor)) {
E
Erich Gamma 已提交
1104 1105 1106
			return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
		}

1107
		const textModel = activeCodeEditor.getModel();
E
Erich Gamma 已提交
1108

B
Benjamin Pasero 已提交
1109
		const EOLOptions: IChangeEOLEntry[] = [
1110 1111
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1112 1113
		];

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

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

export class ChangeEncodingAction extends Action {

M
Matt Bierner 已提交
1130 1131
	public static readonly ID = 'workbench.action.editor.changeEncoding';
	public static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
E
Erich Gamma 已提交
1132 1133 1134 1135

	constructor(
		actionId: string,
		actionLabel: string,
1136
		@INextEditorService private editorService: INextEditorService,
E
Erich Gamma 已提交
1137
		@IQuickOpenService private quickOpenService: IQuickOpenService,
1138
		@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
1139
		@IFileService private fileService: IFileService
E
Erich Gamma 已提交
1140 1141 1142 1143
	) {
		super(actionId, actionLabel);
	}

A
Alex Dima 已提交
1144
	public run(): TPromise<any> {
B
Benjamin Pasero 已提交
1145
		if (!getCodeEditor(this.editorService.activeTextEditorControl)) {
E
Erich Gamma 已提交
1146 1147 1148
			return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}

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

		let pickActionPromise: TPromise<IPickOpenEntry>;
B
Benjamin Pasero 已提交
1156 1157 1158 1159 1160 1161 1162

		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 已提交
1163 1164
			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 已提交
1165
		}
E
Erich Gamma 已提交
1166

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

1175
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1176
			if (!action) {
1177
				return void 0;
E
Erich Gamma 已提交
1178 1179
			}

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

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

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

1193
					const configuredEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding');
1194

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

1214 1215 1216 1217 1218 1219 1220 1221 1222
							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;
							}

1223
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
1224 1225
						});

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

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

1249 1250 1251 1252
class ScreenReaderDetectedExplanation extends Themable {
	private container: HTMLElement;
	private hrElement: HTMLHRElement;
	private _visible: boolean;
1253 1254

	constructor(
1255
		@IThemeService themeService: IThemeService,
1256
		@IContextViewService private readonly contextViewService: IContextViewService,
1257
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
1258
	) {
1259 1260
		super(themeService);
	}
1261

1262 1263 1264
	public get visible(): boolean {
		return this._visible;
	}
1265

1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
	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;
			},
1296 1297 1298
			render: (container) => {
				return this.renderContents(container);
			},
1299
			onDOMEvent: (e, activeElement) => { },
1300
			onHide: () => {
1301
				this._visible = false;
1302 1303 1304 1305
			}
		});
	}

1306 1307
	public hide(): void {
		this.contextViewService.hideContextView();
1308 1309
	}

1310 1311 1312 1313
	protected renderContents(parent: HTMLElement): IDisposable {
		const toDispose: IDisposable[] = [];

		this.container = $('div.screen-reader-detected-explanation', {
1314 1315 1316
			'aria-hidden': 'true'
		});

1317
		const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
1318
		this.container.appendChild(title);
1319 1320

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

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

1348
		const buttonContainer = $('div.buttons');
1349
		this.container.appendChild(buttonContainer);
1350 1351 1352

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

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

		const clear = $('div');
		clear.style.clear = 'both';
1369
		this.container.appendChild(clear);
1370 1371

		const br = $('br');
1372
		this.container.appendChild(br);
1373

1374 1375
		this.hrElement = $('hr');
		this.container.appendChild(this.hrElement);
1376 1377

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

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

1383
		parent.appendChild(this.container);
1384

1385
		this.updateStyles();
1386 1387

		return {
1388
			dispose: () => dispose(toDispose)
1389 1390 1391
		};
	}
}