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

import 'vs/css!./media/editorstatus';
7
import * as nls from 'vs/nls';
8
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
9
import { format } from 'vs/base/common/strings';
B
Benjamin Pasero 已提交
10
import { extname, basename } from 'vs/base/common/resources';
11 12
import { areFunctions, withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
13 14
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Action } from 'vs/base/common/actions';
15
import { Language } from 'vs/base/common/platform';
J
Johannes Rieken 已提交
16
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
17
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor';
M
Matt Bierner 已提交
18
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
19
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
20
import { IEditorAction } from 'vs/editor/common/editorCommon';
21
import { EndOfLineSequence } from 'vs/editor/common/model';
22
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
23 24
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations';
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
J
Johannes Rieken 已提交
25 26
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
27
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
C
Christof Marti 已提交
28
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
29
import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
30
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
A
Alex Dima 已提交
31
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
J
Johannes Rieken 已提交
32
import { IModelService } from 'vs/editor/common/services/modelService';
33
import { Range } from 'vs/editor/common/core/range';
J
Johannes Rieken 已提交
34 35
import { Selection } from 'vs/editor/common/core/selection';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
36
import { ICommandService } from 'vs/platform/commands/common/commands';
37
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
38
import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
39
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
40
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
41
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
42
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
43
import { deepClone } from 'vs/base/common/objects';
B
Benjamin Pasero 已提交
44
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
45
import { Schemas } from 'vs/base/common/network';
S
Sandeep Somavarapu 已提交
46
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
C
Christof Marti 已提交
47
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
48
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
49
import { timeout } from 'vs/base/common/async';
50
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
J
Joao Moreno 已提交
51
import { Event } from 'vs/base/common/event';
52
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
B
Benjamin Pasero 已提交
53

B
Benjamin Pasero 已提交
54 55 56
class SideBySideEditorEncodingSupport implements IEncodingSupport {
	constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }

B
Benjamin Pasero 已提交
57
	getEncoding(): string {
B
Benjamin Pasero 已提交
58
		return this.master.getEncoding(); // always report from modified (right hand) side
59 60
	}

B
Benjamin Pasero 已提交
61
	setEncoding(encoding: string, mode: EncodingMode): void {
62 63 64 65 66 67 68 69 70
		[this.master, this.details].forEach(editor => editor.setEncoding(encoding, mode));
	}
}

class SideBySideEditorModeSupport implements IModeSupport {
	constructor(private master: IModeSupport, private details: IModeSupport) { }

	setMode(mode: string): void {
		[this.master, this.details].forEach(editor => editor.setMode(mode));
B
Benjamin Pasero 已提交
71 72 73
	}
}

M
Matt Bierner 已提交
74
function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null {
B
Benjamin Pasero 已提交
75 76

	// 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
	const encodingSupport = input as IFileEditorInput;
95
	if (areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
96 97 98
		return encodingSupport;
	}

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

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null {

	// Untitled Editor
	if (input instanceof UntitledEditorInput) {
		return input;
	}

	// Side by Side (diff) Editor
	if (input instanceof SideBySideEditorInput) {
		const masterModeSupport = toEditorWithModeSupport(input.master);
		const detailsModeSupport = toEditorWithModeSupport(input.details);

		if (masterModeSupport && detailsModeSupport) {
			return new SideBySideEditorModeSupport(masterModeSupport, detailsModeSupport);
		}

		return masterModeSupport;
	}

	// File or Resource Editor
	const modeSupport = input as IFileEditorInput;
	if (typeof modeSupport.setMode === 'function') {
		return modeSupport;
	}

	// Unsupported for any other editor
	return null;
}

E
Erich Gamma 已提交
132
interface IEditorSelectionStatus {
133
	selections?: Selection[];
E
Erich Gamma 已提交
134 135 136
	charactersSelected?: number;
}

137
class StateChange {
M
Matt Bierner 已提交
138 139 140 141 142 143 144 145
	indentation: boolean = false;
	selectionStatus: boolean = false;
	mode: boolean = false;
	encoding: boolean = false;
	EOL: boolean = false;
	tabFocusMode: boolean = false;
	screenReaderMode: boolean = false;
	metadata: boolean = false;
146

B
Benjamin Pasero 已提交
147
	combine(other: StateChange) {
148 149 150 151 152 153
		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;
154
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
155
		this.metadata = this.metadata || other.metadata;
156
	}
157

158
	hasChanges(): boolean {
159 160 161 162 163 164 165 166 167
		return this.indentation
			|| this.selectionStatus
			|| this.mode
			|| this.encoding
			|| this.EOL
			|| this.tabFocusMode
			|| this.screenReaderMode
			|| this.metadata;
	}
E
Erich Gamma 已提交
168 169
}

170 171 172 173 174
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
175
	indentation?: string;
176
	tabFocusMode?: boolean;
177
	screenReaderMode?: boolean;
178
	metadata?: string | undefined;
179 180 181
}

class State {
182 183
	private _selectionStatus: string | undefined;
	get selectionStatus(): string | undefined { return this._selectionStatus; }
184

185 186
	private _mode: string | undefined;
	get mode(): string | undefined { return this._mode; }
187

188 189
	private _encoding: string | undefined;
	get encoding(): string | undefined { return this._encoding; }
190

191 192
	private _EOL: string | undefined;
	get EOL(): string | undefined { return this._EOL; }
193

194 195
	private _indentation: string | undefined;
	get indentation(): string | undefined { return this._indentation; }
I
isidor 已提交
196

197 198
	private _tabFocusMode: boolean | undefined;
	get tabFocusMode(): boolean | undefined { return this._tabFocusMode; }
199

200 201
	private _screenReaderMode: boolean | undefined;
	get screenReaderMode(): boolean | undefined { return this._screenReaderMode; }
202

203 204
	private _metadata: string | undefined;
	get metadata(): string | undefined { return this._metadata; }
205

206
	constructor() { }
207

208 209
	update(update: StateDelta): StateChange {
		const change = new StateChange();
210

211
		if ('selectionStatus' in update) {
212 213
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
214
				change.selectionStatus = true;
215 216
			}
		}
217

218
		if ('indentation' in update) {
219 220
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
221
				change.indentation = true;
I
isidor 已提交
222 223
			}
		}
224

225
		if ('mode' in update) {
226 227
			if (this._mode !== update.mode) {
				this._mode = update.mode;
228
				change.mode = true;
229 230
			}
		}
231

232
		if ('encoding' in update) {
233 234
			if (this._encoding !== update.encoding) {
				this._encoding = update.encoding;
235
				change.encoding = true;
236 237
			}
		}
238

239
		if ('EOL' in update) {
240 241
			if (this._EOL !== update.EOL) {
				this._EOL = update.EOL;
242
				change.EOL = true;
243 244
			}
		}
245

246
		if ('tabFocusMode' in update) {
247 248
			if (this._tabFocusMode !== update.tabFocusMode) {
				this._tabFocusMode = update.tabFocusMode;
249
				change.tabFocusMode = true;
250 251
			}
		}
252

253
		if ('screenReaderMode' in update) {
254 255
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
256
				change.screenReaderMode = true;
257 258
			}
		}
259

260
		if ('metadata' in update) {
261 262
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
263
				change.metadata = true;
264 265
			}
		}
266

267
		return change;
268 269 270
	}
}

271 272 273 274 275 276
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 已提交
277
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
278
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
A
Alex Dima 已提交
279
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
E
Erich Gamma 已提交
280

281
class StatusBarItem {
282 283 284
	private _showing = true;

	constructor(
M
Matt Bierner 已提交
285 286
		private readonly element: HTMLElement,
		title: string,
287 288
	) {
		this.setVisible(false);
M
Matt Bierner 已提交
289
		this.element.title = title;
290
	}
291

292
	set textContent(value: string) {
293 294
		this.element.textContent = value;
	}
295

296
	set onclick(value: () => void) {
297 298 299
		this.element.onclick = value;
	}

300
	setVisible(shouldShow: boolean): void {
301 302 303 304
		if (shouldShow !== this._showing) {
			this._showing = shouldShow;
			this.element.style.display = shouldShow ? '' : 'none';
		}
A
Alex Dima 已提交
305 306
	}
}
307

308
export class EditorStatus implements IStatusbarItem {
309
	private state: State;
310
	private element: HTMLElement;
311 312 313 314 315 316 317 318
	private tabFocusModeElement: StatusBarItem;
	private screenRedearModeElement: StatusBarItem;
	private indentationElement: StatusBarItem;
	private selectionElement: StatusBarItem;
	private encodingElement: StatusBarItem;
	private eolElement: StatusBarItem;
	private modeElement: StatusBarItem;
	private metadataElement: StatusBarItem;
E
Erich Gamma 已提交
319
	private toDispose: IDisposable[];
320
	private activeEditorListeners: IDisposable[];
M
Matt Bierner 已提交
321 322 323
	private delayedRender: IDisposable | null;
	private toRender: StateChange | null;
	private screenReaderNotification: INotificationHandle | null;
E
Erich Gamma 已提交
324

325
	constructor(
326 327 328 329 330 331
		@IEditorService private readonly editorService: IEditorService,
		@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
		@IModeService private readonly modeService: IModeService,
		@ITextFileService private readonly textFileService: ITextFileService,
332
		@IConfigurationService private readonly configurationService: IConfigurationService,
333 334
		@INotificationService private readonly notificationService: INotificationService,
		@IAccessibilityService private readonly accessibilityService: IAccessibilityService
335 336
	) {
		this.toDispose = [];
337
		this.activeEditorListeners = [];
338
		this.state = new State();
E
Erich Gamma 已提交
339 340
	}

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

M
Matt Bierner 已提交
344 345 346
		this.tabFocusModeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')),
			nls.localize('disableTabMode', "Disable Accessibility Mode"));
347 348 349
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;

M
Matt Bierner 已提交
350 351 352
		this.screenRedearModeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-screenreadermode.status-bar-info')),
			nlsScreenReaderDetectedTitle);
353 354 355
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
		this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();

M
Matt Bierner 已提交
356 357 358
		this.selectionElement = new StatusBarItem(
			append(this.element, $('a.editor-status-selection')),
			nls.localize('gotoLine', "Go to Line"));
359 360
		this.selectionElement.onclick = () => this.onSelectionClick();

M
Matt Bierner 已提交
361 362 363
		this.indentationElement = new StatusBarItem(
			append(this.element, $('a.editor-status-indentation')),
			nls.localize('selectIndentation', "Select Indentation"));
364 365
		this.indentationElement.onclick = () => this.onIndentationClick();

M
Matt Bierner 已提交
366 367 368
		this.encodingElement = new StatusBarItem(
			append(this.element, $('a.editor-status-encoding')),
			nls.localize('selectEncoding', "Select Encoding"));
369 370
		this.encodingElement.onclick = () => this.onEncodingClick();

M
Matt Bierner 已提交
371 372 373
		this.eolElement = new StatusBarItem(
			append(this.element, $('a.editor-status-eol')),
			nls.localize('selectEOL', "Select End of Line Sequence"));
374 375
		this.eolElement.onclick = () => this.onEOLClick();

M
Matt Bierner 已提交
376 377 378
		this.modeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-mode')),
			nls.localize('selectLanguageMode', "Select Language Mode"));
379 380
		this.modeElement.onclick = () => this.onModeClick();

M
Matt Bierner 已提交
381 382 383
		this.metadataElement = new StatusBarItem(
			append(this.element, $('span.editor-status-metadata')),
			nls.localize('fileInfo', "File Information"));
384

385 386 387
		this.delayedRender = null;
		this.toRender = null;

388
		this.toDispose.push(
M
Matt Bierner 已提交
389 390 391 392
			toDisposable(() => {
				if (this.delayedRender) {
					this.delayedRender.dispose();
					this.delayedRender = null;
393
				}
M
Matt Bierner 已提交
394
			}),
395
			this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
396 397
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
398
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
399
		);
E
Erich Gamma 已提交
400

J
Joao Moreno 已提交
401
		return combinedDisposable(this.toDispose);
402 403
	}

404
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
405
		const changed = this.state.update(update);
406
		if (!changed.hasChanges()) {
407 408
			// Nothing really changed
			return;
E
Erich Gamma 已提交
409 410
		}

411 412 413 414
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
415
				const toRender = this.toRender;
416
				this.toRender = null;
M
Matt Bierner 已提交
417 418 419
				if (toRender) {
					this._renderNow(toRender);
				}
420 421 422 423 424 425
			});
		} else {
			this.toRender.combine(changed);
		}
	}

426
	private _renderNow(changed: StateChange): void {
427
		if (changed.tabFocusMode) {
428
			this.tabFocusModeElement.setVisible(!!this.state.tabFocusMode);
E
Erich Gamma 已提交
429 430
		}

431
		if (changed.screenReaderMode) {
432
			this.screenRedearModeElement.setVisible(!!this.state.screenReaderMode);
433 434
		}

I
isidor 已提交
435 436
		if (changed.indentation) {
			if (this.state.indentation) {
437 438
				this.indentationElement.textContent = this.state.indentation;
				this.indentationElement.setVisible(true);
I
isidor 已提交
439
			} else {
440
				this.indentationElement.setVisible(false);
I
isidor 已提交
441 442 443
			}
		}

444
		if (changed.selectionStatus) {
445
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
446 447
				this.selectionElement.textContent = this.state.selectionStatus;
				this.selectionElement.setVisible(true);
448
			} else {
449
				this.selectionElement.setVisible(false);
450
			}
E
Erich Gamma 已提交
451 452
		}

453 454
		if (changed.encoding) {
			if (this.state.encoding) {
455 456
				this.encodingElement.textContent = this.state.encoding;
				this.encodingElement.setVisible(true);
457
			} else {
458
				this.encodingElement.setVisible(false);
459
			}
E
Erich Gamma 已提交
460 461
		}

462 463
		if (changed.EOL) {
			if (this.state.EOL) {
464 465
				this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF;
				this.eolElement.setVisible(true);
466
			} else {
467
				this.eolElement.setVisible(false);
468
			}
E
Erich Gamma 已提交
469 470
		}

471 472
		if (changed.mode) {
			if (this.state.mode) {
473 474
				this.modeElement.textContent = this.state.mode;
				this.modeElement.setVisible(true);
475
			} else {
476
				this.modeElement.setVisible(false);
477 478
			}
		}
479 480 481

		if (changed.metadata) {
			if (this.state.metadata) {
482 483
				this.metadataElement.textContent = this.state.metadata;
				this.metadataElement.setVisible(true);
484
			} else {
485
				this.metadataElement.setVisible(false);
486 487
			}
		}
E
Erich Gamma 已提交
488 489
	}

M
Matt Bierner 已提交
490
	private getSelectionLabel(info: IEditorSelectionStatus): string | undefined {
E
Erich Gamma 已提交
491
		if (!info || !info.selections) {
M
Matt Bierner 已提交
492
			return undefined;
E
Erich Gamma 已提交
493 494 495 496
		}

		if (info.selections.length === 1) {
			if (info.charactersSelected) {
497
				return format(nlsSingleSelectionRange, info.selections[0].positionLineNumber, info.selections[0].positionColumn, info.charactersSelected);
E
Erich Gamma 已提交
498
			}
B
Benjamin Pasero 已提交
499

500
			return format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
501
		}
B
Benjamin Pasero 已提交
502 503

		if (info.charactersSelected) {
504
			return format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected);
B
Benjamin Pasero 已提交
505 506 507
		}

		if (info.selections.length > 0) {
508
			return format(nlsMultiSelection, info.selections.length);
B
Benjamin Pasero 已提交
509 510
		}

M
Matt Bierner 已提交
511
		return undefined;
E
Erich Gamma 已提交
512 513
	}

514
	private onModeClick(): void {
B
Benjamin Pasero 已提交
515
		const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
516

517
		action.run();
518 519 520
		action.dispose();
	}

I
isidor 已提交
521 522
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
523
		action.run();
I
isidor 已提交
524 525 526
		action.dispose();
	}

527
	private onScreenReaderModeClick(): void {
528 529 530
		if (!this.screenReaderNotification) {
			this.screenReaderNotification = this.notificationService.prompt(
				Severity.Info,
A
Alex Dima 已提交
531
				nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"),
532 533 534 535 536 537 538 539 540 541
				[{
					label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
					run: () => {
						this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
					}
				}, {
					label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
					run: () => {
						this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
					}
B
Benjamin Pasero 已提交
542 543
				}],
				{ sticky: true }
544
			);
B
Benjamin Pasero 已提交
545

J
Joao Moreno 已提交
546
			Event.once(this.screenReaderNotification.onDidClose)(() => {
B
Benjamin Pasero 已提交
547 548
				this.screenReaderNotification = null;
			});
549
		}
550 551
	}

552 553 554 555 556
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

	private onEOLClick(): void {
B
Benjamin Pasero 已提交
557
		const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
558

559
		action.run();
560 561 562 563
		action.dispose();
	}

	private onEncodingClick(): void {
B
Benjamin Pasero 已提交
564
		const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
565

566
		action.run();
567 568 569 570
		action.dispose();
	}

	private onTabFocusModeClick(): void {
571
		TabFocus.setTabFocusMode(false);
572 573
	}

574 575
	private updateStatusBar(): void {
		const activeControl = this.editorService.activeControl;
576
		const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined;
577 578

		// Update all states
579 580 581 582 583 584 585
		this.onScreenReaderModeChange(activeCodeEditor);
		this.onSelectionChange(activeCodeEditor);
		this.onModeChange(activeCodeEditor);
		this.onEOLChange(activeCodeEditor);
		this.onEncodingChange(activeControl);
		this.onIndentationChange(activeCodeEditor);
		this.onMetadataChange(activeControl);
E
Erich Gamma 已提交
586

587 588 589 590
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
591
		if (activeCodeEditor) {
592

593
			// Hook Listener for Configuration changes
594
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
595
				if (event.accessibilitySupport) {
596
					this.onScreenReaderModeChange(activeCodeEditor);
597 598 599
				}
			}));

600
			// Hook Listener for Selection changes
601 602
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(activeCodeEditor);
603 604 605
			}));

			// Hook Listener for mode changes
606 607
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
				this.onModeChange(activeCodeEditor);
608 609 610
			}));

			// Hook Listener for content changes
611 612
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => {
				this.onEOLChange(activeCodeEditor);
613

M
Matt Bierner 已提交
614
				const selections = activeCodeEditor.getSelections();
M
Matt Bierner 已提交
615 616 617 618 619 620
				if (selections) {
					for (const change of e.changes) {
						if (selections.some(selection => Range.areIntersecting(selection, change.range))) {
							this.onSelectionChange(activeCodeEditor);
							break;
						}
621 622
					}
				}
623 624 625
			}));

			// Hook Listener for content options changes
626 627
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
				this.onIndentationChange(activeCodeEditor);
628
			}));
E
Erich Gamma 已提交
629
		}
630 631

		// Handle binary editors
632
		else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
633
			const binaryEditors: BaseBinaryResourceEditor[] = [];
634 635
			if (activeControl instanceof BinaryResourceDiffEditor) {
				const details = activeControl.getDetailsEditor();
636 637 638 639
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

640
				const master = activeControl.getMasterEditor();
641 642 643
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
644
			} else {
645
				binaryEditors.push(activeControl);
646 647 648 649
			}

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
650
					this.onMetadataChange(activeControl);
651
				}));
652 653 654 655

				this.activeEditorListeners.push(editor.onDidOpenInPlace(() => {
					this.updateStatusBar();
				}));
656
			});
657
		}
658
	}
E
Erich Gamma 已提交
659

M
Matt Bierner 已提交
660 661
	private onModeChange(editorWidget: ICodeEditor | undefined): void {
		let info: StateDelta = { mode: undefined };
E
Erich Gamma 已提交
662 663

		// We only support text based editors
664
		if (editorWidget) {
A
Alex Dima 已提交
665
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
666 667
			if (textModel) {
				// Compute mode
A
Alex Dima 已提交
668
				const modeId = textModel.getLanguageIdentifier().language;
M
Matt Bierner 已提交
669
				info = { mode: this.modeService.getLanguageName(modeId) || undefined };
E
Erich Gamma 已提交
670 671 672 673 674 675
			}
		}

		this.updateState(info);
	}

M
Matt Bierner 已提交
676 677
	private onIndentationChange(editorWidget: ICodeEditor | undefined): void {
		const update: StateDelta = { indentation: undefined };
678

679
		if (editorWidget) {
A
Alex Dima 已提交
680
			const model = editorWidget.getModel();
681 682 683 684
			if (model) {
				const modelOpts = model.getOptions();
				update.indentation = (
					modelOpts.insertSpaces
D
David Lechner 已提交
685
						? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
686 687
						: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
				);
688
			}
I
isidor 已提交
689
		}
690

I
isidor 已提交
691 692 693
		this.updateState(update);
	}

M
Matt Bierner 已提交
694
	private onMetadataChange(editor: IBaseEditor | undefined): void {
M
Matt Bierner 已提交
695
		const update: StateDelta = { metadata: undefined };
696 697

		if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) {
698
			update.metadata = editor.getMetadata();
699 700 701 702 703
		}

		this.updateState(update);
	}

704
	private promptedScreenReader: boolean = false;
705

M
Matt Bierner 已提交
706
	private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
707 708 709 710
		let screenReaderMode = false;

		// We only support text based editors
		if (editorWidget) {
711
			const screenReaderDetected = (this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled);
712
			if (screenReaderDetected) {
713
				const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
714 715
				if (screenReaderConfiguration === 'auto') {
					// show explanation
716 717
					if (!this.promptedScreenReader) {
						this.promptedScreenReader = true;
718 719 720 721 722 723
						setTimeout(() => {
							this.onScreenReaderModeClick();
						}, 100);
					}
				}
			}
724 725 726 727

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

728 729
		if (screenReaderMode === false && this.screenReaderNotification) {
			this.screenReaderNotification.close();
730 731
		}

732 733 734
		this.updateState({ screenReaderMode: screenReaderMode });
	}

M
Matt Bierner 已提交
735
	private onSelectionChange(editorWidget: ICodeEditor | undefined): void {
B
Benjamin Pasero 已提交
736
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
737 738

		// We only support text based editors
739
		if (editorWidget) {
E
Erich Gamma 已提交
740 741 742 743 744 745

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
746
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
747
			if (textModel) {
748
				info.selections.forEach(selection => {
M
Matt Bierner 已提交
749
					info.charactersSelected! += textModel.getValueLengthInRange(selection);
E
Erich Gamma 已提交
750 751 752 753 754
				});
			}

			// Compute the visible column for one selection. This will properly handle tabs and their configured widths
			if (info.selections.length === 1) {
M
Matt Bierner 已提交
755
				const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()!);
E
Erich Gamma 已提交
756

757 758 759 760 761 762 763
				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 已提交
764 765 766 767 768

				info.selections[0] = selectionClone;
			}
		}

769
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
770 771
	}

M
Matt Bierner 已提交
772 773
	private onEOLChange(editorWidget: ICodeEditor | undefined): void {
		const info: StateDelta = { EOL: undefined };
A
Alex Dima 已提交
774

A
Alex Dima 已提交
775 776
		if (editorWidget && !editorWidget.getConfiguration().readOnly) {
			const codeEditorModel = editorWidget.getModel();
I
isidor 已提交
777 778 779
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
780 781 782 783 784
		}

		this.updateState(info);
	}

785
	private onEncodingChange(e?: IBaseEditor): void {
E
Erich Gamma 已提交
786 787 788 789
		if (e && !this.isActiveEditor(e)) {
			return;
		}

M
Matt Bierner 已提交
790
		const info: StateDelta = { encoding: undefined };
E
Erich Gamma 已提交
791 792

		// We only support text based editors
793
		if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
M
Matt Bierner 已提交
794
			const encodingSupport: IEncodingSupport | null = e.input ? toEditorWithEncodingSupport(e.input) : null;
795
			if (encodingSupport) {
B
Benjamin Pasero 已提交
796 797
				const rawEncoding = encodingSupport.getEncoding();
				const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
798 799 800 801 802 803 804 805 806 807 808
				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);
	}

809
	private onResourceEncodingChange(resource: URI): void {
810 811
		const activeControl = this.editorService.activeControl;
		if (activeControl) {
B
Benjamin Pasero 已提交
812
			const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER });
B
Benjamin Pasero 已提交
813
			if (activeResource && activeResource.toString() === resource.toString()) {
B
Benjamin Pasero 已提交
814
				return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource
815 816
			}
		}
E
Erich Gamma 已提交
817 818
	}

819
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
820
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
821 822 823 824

		this.updateState(info);
	}

825 826
	private isActiveEditor(control: IBaseEditor): boolean {
		const activeControl = this.editorService.activeControl;
E
Erich Gamma 已提交
827

828
		return !!activeControl && activeControl === control;
E
Erich Gamma 已提交
829 830 831
	}
}

M
Matt Bierner 已提交
832
function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean {
A
Alex Dima 已提交
833
	if (!codeEditor) {
S
Sandeep Somavarapu 已提交
834 835
		return false;
	}
A
Alex Dima 已提交
836 837 838
	const config = codeEditor.getConfiguration();
	return (!config.readOnly);
}
S
Sandeep Somavarapu 已提交
839

A
Alex Dima 已提交
840
function isWritableBaseEditor(e: IBaseEditor): boolean {
M
Matt Bierner 已提交
841
	return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined);
E
Erich Gamma 已提交
842 843
}

844 845
export class ShowLanguageExtensionsAction extends Action {

846
	static readonly ID = 'workbench.action.showLanguageExtensions';
847 848

	constructor(
849
		private fileExtension: string,
850
		@ICommandService private readonly commandService: ICommandService,
851 852
		@IExtensionGalleryService galleryService: IExtensionGalleryService
	) {
853 854
		super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));

855 856 857
		this.enabled = galleryService.isEnabled();
	}

858 859
	async run(): Promise<void> {
		await this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension);
860 861 862
	}
}

E
Erich Gamma 已提交
863 864
export class ChangeModeAction extends Action {

B
Benjamin Pasero 已提交
865 866
	static readonly ID = 'workbench.action.editor.changeLanguageMode';
	static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
E
Erich Gamma 已提交
867 868 869 870

	constructor(
		actionId: string,
		actionLabel: string,
871 872 873
		@IModeService private readonly modeService: IModeService,
		@IModelService private readonly modelService: IModelService,
		@IEditorService private readonly editorService: IEditorService,
874
		@IConfigurationService private readonly configurationService: IConfigurationService,
875 876 877 878
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@IPreferencesService private readonly preferencesService: IPreferencesService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService
E
Erich Gamma 已提交
879 880 881 882
	) {
		super(actionId, actionLabel);
	}

883
	async run(): Promise<any> {
884 885
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
886
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
E
Erich Gamma 已提交
887 888
		}

889
		const textModel = activeTextEditorWidget.getModel();
B
Benjamin Pasero 已提交
890
		const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null;
B
Benjamin Pasero 已提交
891 892

		let hasLanguageSupport = !!resource;
M
Matt Bierner 已提交
893
		if (resource && resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
B
Benjamin Pasero 已提交
894
			hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
895
		}
E
Erich Gamma 已提交
896 897

		// Compute mode
M
Matt Bierner 已提交
898
		let currentModeId: string | undefined;
899
		let modeId: string;
900
		if (textModel) {
901
			modeId = textModel.getLanguageIdentifier().language;
M
Matt Bierner 已提交
902
			currentModeId = this.modeService.getLanguageName(modeId) || undefined;
E
Erich Gamma 已提交
903 904 905
		}

		// All languages are valid picks
906
		const languages = this.modeService.getRegisteredLanguageNames();
C
Christof Marti 已提交
907
		const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
908 909 910 911 912 913
			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()));
			}
914

915
			// construct a fake resource to be able to show nice icons if any
916
			let fakeResource: URI | undefined;
917 918
			const extensions = this.modeService.getExtensions(lang);
			if (extensions && extensions.length) {
919
				fakeResource = URI.file(extensions[0]);
920 921 922
			} else {
				const filenames = this.modeService.getFilenames(lang);
				if (filenames && filenames.length) {
923
					fakeResource = URI.file(filenames[0]);
924 925 926
				}
			}

B
Benjamin Pasero 已提交
927
			return {
928
				label: lang,
C
Christof Marti 已提交
929
				iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
B
Benjamin Pasero 已提交
930
				description
E
Erich Gamma 已提交
931 932
			};
		});
933

B
Benjamin Pasero 已提交
934
		if (hasLanguageSupport) {
C
Christof Marti 已提交
935
			picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
936
		}
E
Erich Gamma 已提交
937

938
		// Offer action to configure via settings
C
Christof Marti 已提交
939 940
		let configureModeAssociations: IQuickPickItem;
		let configureModeSettings: IQuickPickItem;
941
		let galleryAction: Action;
M
Matt Bierner 已提交
942
		if (hasLanguageSupport && resource) {
B
Benjamin Pasero 已提交
943
			const ext = extname(resource) || basename(resource);
S
Sandeep Somavarapu 已提交
944 945 946 947 948 949

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

950 951
			configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
			picks.unshift(configureModeSettings);
952
			configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
953 954
			picks.unshift(configureModeAssociations);
		}
E
Erich Gamma 已提交
955

956
		// Offer to "Auto Detect"
C
Christof Marti 已提交
957
		const autoDetectMode: IQuickPickItem = {
958 959
			label: nls.localize('autoDetect', "Auto Detect")
		};
960

B
Benjamin Pasero 已提交
961
		if (hasLanguageSupport) {
962 963
			picks.unshift(autoDetectMode);
		}
964

C
Christof Marti 已提交
965
		return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
966 967 968
			if (!pick) {
				return;
			}
B
Benjamin Pasero 已提交
969

970 971 972 973
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
974

975 976
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
M
Matt Bierner 已提交
977 978 979
				if (resource) {
					this.configureFileAssociation(resource);
				}
980 981
				return;
			}
982

983 984
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
985
				this.preferencesService.configureSettingsForLanguage(modeId);
986 987 988
				return;
			}

989
			// Change mode for active editor
B
Benjamin Pasero 已提交
990
			const activeEditor = this.editorService.activeEditor;
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
			if (activeEditor) {
				const modeSupport = toEditorWithModeSupport(activeEditor);
				if (modeSupport) {

					// Find mode
					let languageSelection: ILanguageSelection | undefined;
					if (pick === autoDetectMode) {
						if (textModel) {
							const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
							if (resource) {
								languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1));
							}
						}
					} else {
						languageSelection = this.modeService.createByLanguageName(pick.label);
1006
					}
1007

1008 1009 1010
					// Change mode
					if (typeof languageSelection !== 'undefined') {
						modeSupport.setMode(languageSelection.languageIdentifier.language);
M
Matt Bierner 已提交
1011 1012 1013
					}
				}
			}
E
Erich Gamma 已提交
1014 1015
		});
	}
1016

1017
	private configureFileAssociation(resource: URI): void {
B
Benjamin Pasero 已提交
1018 1019 1020
		const extension = extname(resource);
		const base = basename(resource);
		const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(base);
1021

1022
		const languages = this.modeService.getRegisteredLanguageNames();
C
Christof Marti 已提交
1023
		const picks: IQuickPickItem[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
1024
			const id = withNullAsUndefined(this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
1025

B
Benjamin Pasero 已提交
1026
			return {
1027 1028
				id,
				label: lang,
R
Rob Lourens 已提交
1029
				description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined
1030 1031 1032
			};
		});

1033 1034 1035 1036
		setTimeout(async () => {
			const language = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) });
			if (language) {
				const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG);
1037

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

1045 1046 1047 1048 1049
				// 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;
				}
1050

1051 1052 1053
				// Make sure to write into the value of the target and not the merged value from USER and WORKSPACE config
				const currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user) || Object.create(null);
				currentAssociations[associationKey] = language.id;
1054

1055 1056
				this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
			}
1057
		}, 50 /* quick open is sensitive to being opened so soon after another */);
1058
	}
E
Erich Gamma 已提交
1059 1060
}

C
Christof Marti 已提交
1061
export interface IChangeEOLEntry extends IQuickPickItem {
E
Erich Gamma 已提交
1062 1063 1064
	eol: EndOfLineSequence;
}

I
isidor 已提交
1065
class ChangeIndentationAction extends Action {
I
isidor 已提交
1066

B
Benjamin Pasero 已提交
1067 1068
	static readonly ID = 'workbench.action.editor.changeIndentation';
	static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1069 1070 1071 1072

	constructor(
		actionId: string,
		actionLabel: string,
1073 1074
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
I
isidor 已提交
1075 1076 1077 1078
	) {
		super(actionId, actionLabel);
	}

1079
	async run(): Promise<any> {
1080 1081
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
1082
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
I
isidor 已提交
1083
		}
B
Benjamin Pasero 已提交
1084

1085
		if (!isWritableCodeEditor(activeTextEditorWidget)) {
C
Christof Marti 已提交
1086
			return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
I
isidor 已提交
1087
		}
1088

C
Christof Marti 已提交
1089
		const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
1090 1091 1092 1093 1094 1095
			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 已提交
1096
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
1097 1098
			return {
				id: a.id,
B
Benjamin Pasero 已提交
1099
				label: a.label,
1100
				detail: Language.isDefaultVariant() ? undefined : a.alias,
1101
				run: () => {
1102
					activeTextEditorWidget.focus();
1103 1104
					a.run();
				}
B
Benjamin Pasero 已提交
1105 1106 1107
			};
		});

C
Christof Marti 已提交
1108
		picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
C
Christof Marti 已提交
1109
		picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
I
isidor 已提交
1110

1111 1112
		const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
		return action && action.run();
I
isidor 已提交
1113 1114 1115
	}
}

E
Erich Gamma 已提交
1116 1117
export class ChangeEOLAction extends Action {

B
Benjamin Pasero 已提交
1118 1119
	static readonly ID = 'workbench.action.editor.changeEOL';
	static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
E
Erich Gamma 已提交
1120 1121 1122 1123

	constructor(
		actionId: string,
		actionLabel: string,
1124 1125
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
E
Erich Gamma 已提交
1126 1127 1128 1129
	) {
		super(actionId, actionLabel);
	}

1130
	async run(): Promise<any> {
1131 1132
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
1133
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
E
Erich Gamma 已提交
1134 1135
		}

1136
		if (!isWritableCodeEditor(activeTextEditorWidget)) {
C
Christof Marti 已提交
1137
			return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
E
Erich Gamma 已提交
1138 1139
		}

1140
		let textModel = activeTextEditorWidget.getModel();
E
Erich Gamma 已提交
1141

B
Benjamin Pasero 已提交
1142
		const EOLOptions: IChangeEOLEntry[] = [
1143 1144
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1145 1146
		];

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

1149 1150 1151 1152 1153 1154
		const eol = await this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] });
		if (eol) {
			const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
			if (activeCodeEditor && activeCodeEditor.hasModel() && isWritableCodeEditor(activeCodeEditor)) {
				textModel = activeCodeEditor.getModel();
				textModel.pushEOL(eol.eol);
E
Erich Gamma 已提交
1155
			}
1156
		}
E
Erich Gamma 已提交
1157 1158 1159 1160 1161
	}
}

export class ChangeEncodingAction extends Action {

B
Benjamin Pasero 已提交
1162 1163
	static readonly ID = 'workbench.action.editor.changeEncoding';
	static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
E
Erich Gamma 已提交
1164 1165 1166 1167

	constructor(
		actionId: string,
		actionLabel: string,
1168 1169 1170
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
B
Benjamin Pasero 已提交
1171 1172
		@IFileService private readonly fileService: IFileService,
		@ITextFileService private readonly textFileService: ITextFileService
E
Erich Gamma 已提交
1173 1174 1175 1176
	) {
		super(actionId, actionLabel);
	}

J
Johannes Rieken 已提交
1177
	run(): Promise<any> {
1178
		if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
C
Christof Marti 已提交
1179
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
E
Erich Gamma 已提交
1180 1181
		}

M
Matt Bierner 已提交
1182
		const activeControl = this.editorService.activeControl;
M
Matt Bierner 已提交
1183 1184 1185
		if (!activeControl) {
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
1186

M
Matt Bierner 已提交
1187
		const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input);
1188
		if (!encodingSupport) {
C
Christof Marti 已提交
1189
			return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
E
Erich Gamma 已提交
1190 1191
		}

C
Christof Marti 已提交
1192 1193
		let saveWithEncodingPick: IQuickPickItem;
		let reopenWithEncodingPick: IQuickPickItem;
1194
		if (Language.isDefaultVariant()) {
B
Benjamin Pasero 已提交
1195 1196 1197
			saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
			reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
		} else {
B
Benjamin Pasero 已提交
1198 1199
			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 已提交
1200
		}
E
Erich Gamma 已提交
1201

B
Benjamin Pasero 已提交
1202
		let pickActionPromise: Promise<IQuickPickItem>;
1203
		if (encodingSupport instanceof UntitledEditorInput) {
B
Benjamin Pasero 已提交
1204
			pickActionPromise = Promise.resolve(saveWithEncodingPick);
1205
		} else if (!isWritableBaseEditor(activeControl)) {
B
Benjamin Pasero 已提交
1206
			pickActionPromise = Promise.resolve(reopenWithEncodingPick);
E
Erich Gamma 已提交
1207
		} else {
C
Christof Marti 已提交
1208
			pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
E
Erich Gamma 已提交
1209 1210
		}

1211
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1212
			if (!action) {
R
Rob Lourens 已提交
1213
				return undefined;
E
Erich Gamma 已提交
1214 1215
			}

B
Benjamin Pasero 已提交
1216
			const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER });
1217

1218
			return timeout(50 /* quick open is sensitive to being opened so soon after another */)
1219
				.then(() => {
1220
					if (!resource || !this.fileService.canHandleResource(resource)) {
B
Benjamin Pasero 已提交
1221
						return Promise.resolve(null); // encoding detection only possible for resources the file service can handle
1222 1223
					}

B
Benjamin Pasero 已提交
1224
					return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
1225
				})
B
Benjamin Pasero 已提交
1226
				.then((guessedEncoding: string) => {
1227
					const isReopenWithEncoding = (action === reopenWithEncodingPick);
1228

1229
					const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding');
1230

M
Matt Bierner 已提交
1231
					let directMatchIndex: number | undefined;
M
Matt Bierner 已提交
1232
					let aliasMatchIndex: number | undefined;
1233 1234

					// All encodings are valid picks
C
Christof Marti 已提交
1235
					const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
						.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 已提交
1246 1247 1248 1249
							if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
								return false; // do not show encoding if it is the guessed encoding that does not match the configured
							}

1250 1251 1252 1253 1254 1255 1256 1257 1258
							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;
							}

1259
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
1260 1261
						});

C
Christof Marti 已提交
1262 1263
					const items = picks.slice() as IQuickPickItem[];

B
Benjamin Pasero 已提交
1264 1265
					// If we have a guessed encoding, show it first unless it matches the configured encoding
					if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
C
Christof Marti 已提交
1266
						picks.unshift({ type: 'separator' });
B
Benjamin Pasero 已提交
1267
						picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
1268
					}
1269

C
Christof Marti 已提交
1270
					return this.quickInputService.pick(picks, {
1271
						placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
C
Christof Marti 已提交
1272
						activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
1273
					}).then(encoding => {
M
Matt Bierner 已提交
1274 1275 1276
						if (!encoding) {
							return;
						}
1277

M
Matt Bierner 已提交
1278 1279 1280 1281
						const activeControl = this.editorService.activeControl;
						if (!activeControl) {
							return;
						}
1282

M
Matt Bierner 已提交
1283 1284 1285
						const encodingSupport = toEditorWithEncodingSupport(activeControl.input);
						if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
							encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
1286
						}
1287
					});
E
Erich Gamma 已提交
1288 1289 1290
				});
		});
	}
J
Johannes Rieken 已提交
1291
}