editorStatus.ts 45.6 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';
B
Benjamin Pasero 已提交
17
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor } 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';
B
Benjamin Pasero 已提交
21
import { EndOfLineSequence, ITextModel } 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 {
B
Benjamin Pasero 已提交
62
		[this.master, this.details].forEach(s => s.setEncoding(encoding, mode));
B
Benjamin Pasero 已提交
63 64 65
	}
}

M
Matt Bierner 已提交
66
function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null {
B
Benjamin Pasero 已提交
67 68

	// Untitled Editor
69 70 71 72
	if (input instanceof UntitledEditorInput) {
		return input;
	}

B
Benjamin Pasero 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85
	// 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
B
Benjamin Pasero 已提交
86
	let encodingSupport = input as IFileEditorInput;
87
	if (areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
88 89 90
		return encodingSupport;
	}

B
Benjamin Pasero 已提交
91
	// Unsupported for any other editor
92
	return null;
93 94
}

E
Erich Gamma 已提交
95
interface IEditorSelectionStatus {
96
	selections?: Selection[];
E
Erich Gamma 已提交
97 98 99
	charactersSelected?: number;
}

100
class StateChange {
B
Benjamin Pasero 已提交
101 102
	_stateChangeBrand: void;

M
Matt Bierner 已提交
103 104 105 106 107 108 109 110
	indentation: boolean = false;
	selectionStatus: boolean = false;
	mode: boolean = false;
	encoding: boolean = false;
	EOL: boolean = false;
	tabFocusMode: boolean = false;
	screenReaderMode: boolean = false;
	metadata: boolean = false;
111

B
Benjamin Pasero 已提交
112
	combine(other: StateChange) {
113 114 115 116 117 118
		this.indentation = this.indentation || other.indentation;
		this.selectionStatus = this.selectionStatus || other.selectionStatus;
		this.mode = this.mode || other.mode;
		this.encoding = this.encoding || other.encoding;
		this.EOL = this.EOL || other.EOL;
		this.tabFocusMode = this.tabFocusMode || other.tabFocusMode;
119
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
120
		this.metadata = this.metadata || other.metadata;
121
	}
122

B
Benjamin Pasero 已提交
123
	public hasChanges(): boolean {
124 125 126 127 128 129 130 131 132
		return this.indentation
			|| this.selectionStatus
			|| this.mode
			|| this.encoding
			|| this.EOL
			|| this.tabFocusMode
			|| this.screenReaderMode
			|| this.metadata;
	}
E
Erich Gamma 已提交
133 134
}

135 136 137 138 139
interface StateDelta {
	selectionStatus?: string;
	mode?: string;
	encoding?: string;
	EOL?: string;
140
	indentation?: string;
141
	tabFocusMode?: boolean;
142
	screenReaderMode?: boolean;
143
	metadata?: string | undefined;
144 145 146
}

class State {
147 148
	private _selectionStatus: string | undefined;
	get selectionStatus(): string | undefined { return this._selectionStatus; }
149

150 151
	private _mode: string | undefined;
	get mode(): string | undefined { return this._mode; }
152

153 154
	private _encoding: string | undefined;
	get encoding(): string | undefined { return this._encoding; }
155

156 157
	private _EOL: string | undefined;
	get EOL(): string | undefined { return this._EOL; }
158

159 160
	private _indentation: string | undefined;
	get indentation(): string | undefined { return this._indentation; }
I
isidor 已提交
161

162 163
	private _tabFocusMode: boolean | undefined;
	get tabFocusMode(): boolean | undefined { return this._tabFocusMode; }
164

165 166
	private _screenReaderMode: boolean | undefined;
	get screenReaderMode(): boolean | undefined { return this._screenReaderMode; }
167

168 169
	private _metadata: string | undefined;
	get metadata(): string | undefined { return this._metadata; }
170

171
	constructor() { }
172

173 174
	update(update: StateDelta): StateChange {
		const change = new StateChange();
175

176
		if ('selectionStatus' in update) {
177 178
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
179
				change.selectionStatus = true;
180 181
			}
		}
182
		if ('indentation' in update) {
183 184
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
185
				change.indentation = true;
I
isidor 已提交
186 187
			}
		}
188
		if ('mode' in update) {
189 190
			if (this._mode !== update.mode) {
				this._mode = update.mode;
191
				change.mode = true;
192 193
			}
		}
194
		if ('encoding' in update) {
195 196
			if (this._encoding !== update.encoding) {
				this._encoding = update.encoding;
197
				change.encoding = true;
198 199
			}
		}
200
		if ('EOL' in update) {
201 202
			if (this._EOL !== update.EOL) {
				this._EOL = update.EOL;
203
				change.EOL = true;
204 205
			}
		}
206
		if ('tabFocusMode' in update) {
207 208
			if (this._tabFocusMode !== update.tabFocusMode) {
				this._tabFocusMode = update.tabFocusMode;
209
				change.tabFocusMode = true;
210 211
			}
		}
212
		if ('screenReaderMode' in update) {
213 214
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
215
				change.screenReaderMode = true;
216 217
			}
		}
218
		if ('metadata' in update) {
219 220
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
221
				change.metadata = true;
222 223
			}
		}
224

225
		return change;
226 227 228
	}
}

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

B
Benjamin Pasero 已提交
239

240
class StatusBarItem {
241 242 243
	private _showing = true;

	constructor(
M
Matt Bierner 已提交
244 245
		private readonly element: HTMLElement,
		title: string,
246 247
	) {
		this.setVisible(false);
M
Matt Bierner 已提交
248
		this.element.title = title;
249
	}
250

B
Benjamin Pasero 已提交
251
	public set textContent(value: string) {
252 253
		this.element.textContent = value;
	}
254

B
Benjamin Pasero 已提交
255
	public set onclick(value: () => void) {
256 257 258
		this.element.onclick = value;
	}

B
Benjamin Pasero 已提交
259
	public setVisible(shouldShow: boolean): void {
260 261 262 263
		if (shouldShow !== this._showing) {
			this._showing = shouldShow;
			this.element.style.display = shouldShow ? '' : 'none';
		}
A
Alex Dima 已提交
264 265
	}
}
266

B
Benjamin Pasero 已提交
267

268
export class EditorStatus implements IStatusbarItem {
269
	private state: State;
270
	private element: HTMLElement;
271 272 273 274 275 276 277 278
	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 已提交
279
	private toDispose: IDisposable[];
280
	private activeEditorListeners: IDisposable[];
M
Matt Bierner 已提交
281 282 283
	private delayedRender: IDisposable | null;
	private toRender: StateChange | null;
	private screenReaderNotification: INotificationHandle | null;
E
Erich Gamma 已提交
284

285
	constructor(
286 287 288 289 290 291
		@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,
292
		@IConfigurationService private readonly configurationService: IConfigurationService,
293 294
		@INotificationService private readonly notificationService: INotificationService,
		@IAccessibilityService private readonly accessibilityService: IAccessibilityService
295 296
	) {
		this.toDispose = [];
297
		this.activeEditorListeners = [];
298
		this.state = new State();
E
Erich Gamma 已提交
299 300
	}

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

M
Matt Bierner 已提交
304 305 306
		this.tabFocusModeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')),
			nls.localize('disableTabMode', "Disable Accessibility Mode"));
307 308 309
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;

M
Matt Bierner 已提交
310 311 312
		this.screenRedearModeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-screenreadermode.status-bar-info')),
			nlsScreenReaderDetectedTitle);
313 314 315
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
		this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();

M
Matt Bierner 已提交
316 317 318
		this.selectionElement = new StatusBarItem(
			append(this.element, $('a.editor-status-selection')),
			nls.localize('gotoLine', "Go to Line"));
319 320
		this.selectionElement.onclick = () => this.onSelectionClick();

M
Matt Bierner 已提交
321 322 323
		this.indentationElement = new StatusBarItem(
			append(this.element, $('a.editor-status-indentation')),
			nls.localize('selectIndentation', "Select Indentation"));
324 325
		this.indentationElement.onclick = () => this.onIndentationClick();

M
Matt Bierner 已提交
326 327 328
		this.encodingElement = new StatusBarItem(
			append(this.element, $('a.editor-status-encoding')),
			nls.localize('selectEncoding', "Select Encoding"));
329 330
		this.encodingElement.onclick = () => this.onEncodingClick();

M
Matt Bierner 已提交
331 332 333
		this.eolElement = new StatusBarItem(
			append(this.element, $('a.editor-status-eol')),
			nls.localize('selectEOL', "Select End of Line Sequence"));
334 335
		this.eolElement.onclick = () => this.onEOLClick();

M
Matt Bierner 已提交
336 337 338
		this.modeElement = new StatusBarItem(
			append(this.element, $('a.editor-status-mode')),
			nls.localize('selectLanguageMode', "Select Language Mode"));
339 340
		this.modeElement.onclick = () => this.onModeClick();

M
Matt Bierner 已提交
341 342 343
		this.metadataElement = new StatusBarItem(
			append(this.element, $('span.editor-status-metadata')),
			nls.localize('fileInfo', "File Information"));
344

345 346 347
		this.delayedRender = null;
		this.toRender = null;

348
		this.toDispose.push(
M
Matt Bierner 已提交
349 350 351 352
			toDisposable(() => {
				if (this.delayedRender) {
					this.delayedRender.dispose();
					this.delayedRender = null;
353
				}
M
Matt Bierner 已提交
354
			}),
355
			this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
356 357
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
358
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
359
		);
E
Erich Gamma 已提交
360

J
Joao Moreno 已提交
361
		return combinedDisposable(this.toDispose);
362 363
	}

364
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
365
		const changed = this.state.update(update);
366
		if (!changed.hasChanges()) {
367 368
			// Nothing really changed
			return;
E
Erich Gamma 已提交
369 370
		}

371 372 373 374
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
375
				const toRender = this.toRender;
376
				this.toRender = null;
M
Matt Bierner 已提交
377 378 379
				if (toRender) {
					this._renderNow(toRender);
				}
380 381 382 383 384 385
			});
		} else {
			this.toRender.combine(changed);
		}
	}

386
	private _renderNow(changed: StateChange): void {
387
		if (changed.tabFocusMode) {
388
			this.tabFocusModeElement.setVisible(!!this.state.tabFocusMode);
E
Erich Gamma 已提交
389 390
		}

391
		if (changed.screenReaderMode) {
392
			this.screenRedearModeElement.setVisible(!!this.state.screenReaderMode);
393 394
		}

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

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

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

422 423
		if (changed.EOL) {
			if (this.state.EOL) {
424 425
				this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF;
				this.eolElement.setVisible(true);
426
			} else {
427
				this.eolElement.setVisible(false);
428
			}
E
Erich Gamma 已提交
429 430
		}

431 432
		if (changed.mode) {
			if (this.state.mode) {
433 434
				this.modeElement.textContent = this.state.mode;
				this.modeElement.setVisible(true);
435
			} else {
436
				this.modeElement.setVisible(false);
437 438
			}
		}
439 440 441

		if (changed.metadata) {
			if (this.state.metadata) {
442 443
				this.metadataElement.textContent = this.state.metadata;
				this.metadataElement.setVisible(true);
444
			} else {
445
				this.metadataElement.setVisible(false);
446 447
			}
		}
E
Erich Gamma 已提交
448 449
	}

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

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

460
			return format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
461
		}
B
Benjamin Pasero 已提交
462 463

		if (info.charactersSelected) {
464
			return format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected);
B
Benjamin Pasero 已提交
465 466 467
		}

		if (info.selections.length > 0) {
468
			return format(nlsMultiSelection, info.selections.length);
B
Benjamin Pasero 已提交
469 470
		}

M
Matt Bierner 已提交
471
		return undefined;
E
Erich Gamma 已提交
472 473
	}

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

477
		action.run();
478 479 480
		action.dispose();
	}

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

487
	private onScreenReaderModeClick(): void {
488 489 490
		if (!this.screenReaderNotification) {
			this.screenReaderNotification = this.notificationService.prompt(
				Severity.Info,
A
Alex Dima 已提交
491
				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)"),
492 493 494 495 496 497 498 499 500 501
				[{
					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 已提交
502 503
				}],
				{ sticky: true }
504
			);
B
Benjamin Pasero 已提交
505

J
Joao Moreno 已提交
506
			Event.once(this.screenReaderNotification.onDidClose)(() => {
B
Benjamin Pasero 已提交
507 508
				this.screenReaderNotification = null;
			});
509
		}
510 511
	}

512 513 514 515 516
	private onSelectionClick(): void {
		this.quickOpenService.show(':'); // "Go to line"
	}

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

519
		action.run();
520 521 522 523
		action.dispose();
	}

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

526
		action.run();
527 528 529 530
		action.dispose();
	}

	private onTabFocusModeClick(): void {
531
		TabFocus.setTabFocusMode(false);
532 533
	}

534 535
	private updateStatusBar(): void {
		const activeControl = this.editorService.activeControl;
536
		const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined;
537 538

		// Update all states
539 540 541 542 543 544 545
		this.onScreenReaderModeChange(activeCodeEditor);
		this.onSelectionChange(activeCodeEditor);
		this.onModeChange(activeCodeEditor);
		this.onEOLChange(activeCodeEditor);
		this.onEncodingChange(activeControl);
		this.onIndentationChange(activeCodeEditor);
		this.onMetadataChange(activeControl);
E
Erich Gamma 已提交
546

547 548 549 550
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
551
		if (activeCodeEditor) {
552

553
			// Hook Listener for Configuration changes
554
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
555
				if (event.accessibilitySupport) {
556
					this.onScreenReaderModeChange(activeCodeEditor);
557 558 559
				}
			}));

560
			// Hook Listener for Selection changes
561 562
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
				this.onSelectionChange(activeCodeEditor);
563 564 565
			}));

			// Hook Listener for mode changes
566 567
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
				this.onModeChange(activeCodeEditor);
568 569 570
			}));

			// Hook Listener for content changes
571 572
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => {
				this.onEOLChange(activeCodeEditor);
573

M
Matt Bierner 已提交
574
				const selections = activeCodeEditor.getSelections();
M
Matt Bierner 已提交
575 576 577 578 579 580
				if (selections) {
					for (const change of e.changes) {
						if (selections.some(selection => Range.areIntersecting(selection, change.range))) {
							this.onSelectionChange(activeCodeEditor);
							break;
						}
581 582
					}
				}
583 584 585
			}));

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

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

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

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
610
					this.onMetadataChange(activeControl);
611
				}));
612 613 614 615

				this.activeEditorListeners.push(editor.onDidOpenInPlace(() => {
					this.updateStatusBar();
				}));
616
			});
617
		}
618
	}
E
Erich Gamma 已提交
619

M
Matt Bierner 已提交
620 621
	private onModeChange(editorWidget: ICodeEditor | undefined): void {
		let info: StateDelta = { mode: undefined };
E
Erich Gamma 已提交
622 623

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

		this.updateState(info);
	}

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

639
		if (editorWidget) {
A
Alex Dima 已提交
640
			const model = editorWidget.getModel();
641 642 643 644
			if (model) {
				const modelOpts = model.getOptions();
				update.indentation = (
					modelOpts.insertSpaces
D
David Lechner 已提交
645
						? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize)
646 647
						: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
				);
648
			}
I
isidor 已提交
649
		}
650

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

M
Matt Bierner 已提交
654
	private onMetadataChange(editor: IBaseEditor | undefined): void {
M
Matt Bierner 已提交
655
		const update: StateDelta = { metadata: undefined };
656 657

		if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) {
658
			update.metadata = editor.getMetadata();
659 660 661 662 663
		}

		this.updateState(update);
	}

B
Benjamin Pasero 已提交
664
	private _promptedScreenReader: boolean = false;
665

M
Matt Bierner 已提交
666
	private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
667 668 669 670
		let screenReaderMode = false;

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

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

688 689
		if (screenReaderMode === false && this.screenReaderNotification) {
			this.screenReaderNotification.close();
690 691
		}

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

M
Matt Bierner 已提交
695
	private onSelectionChange(editorWidget: ICodeEditor | undefined): void {
B
Benjamin Pasero 已提交
696
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
697 698

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

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
706
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
707
			if (textModel) {
708
				info.selections.forEach(selection => {
M
Matt Bierner 已提交
709
					info.charactersSelected! += textModel.getValueLengthInRange(selection);
E
Erich Gamma 已提交
710 711 712 713 714
				});
			}

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

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

				info.selections[0] = selectionClone;
			}
		}

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

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

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

		this.updateState(info);
	}

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

M
Matt Bierner 已提交
750
		const info: StateDelta = { encoding: undefined };
E
Erich Gamma 已提交
751 752

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

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

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

		this.updateState(info);
	}

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

788
		return !!activeControl && activeControl === control;
E
Erich Gamma 已提交
789 790 791
	}
}

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

A
Alex Dima 已提交
800
function isWritableBaseEditor(e: IBaseEditor): boolean {
M
Matt Bierner 已提交
801
	return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined);
E
Erich Gamma 已提交
802 803
}

804 805
export class ShowLanguageExtensionsAction extends Action {

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

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

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

J
Johannes Rieken 已提交
818
	run(): Promise<void> {
R
Rob Lourens 已提交
819
		return this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension).then(() => undefined);
820 821 822
	}
}

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

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

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

J
Johannes Rieken 已提交
843
	run(): Promise<any> {
844 845
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
846
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
E
Erich Gamma 已提交
847 848
		}

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

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

		// Compute mode
M
Matt Bierner 已提交
858
		let currentModeId: string | undefined;
859
		let modeId: string;
860
		if (textModel) {
861
			modeId = textModel.getLanguageIdentifier().language;
M
Matt Bierner 已提交
862
			currentModeId = this.modeService.getLanguageName(modeId) || undefined;
E
Erich Gamma 已提交
863 864 865
		}

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

875
			// construct a fake resource to be able to show nice icons if any
876
			let fakeResource: URI | undefined;
877 878
			const extensions = this.modeService.getExtensions(lang);
			if (extensions && extensions.length) {
879
				fakeResource = URI.file(extensions[0]);
880 881 882
			} else {
				const filenames = this.modeService.getFilenames(lang);
				if (filenames && filenames.length) {
883
					fakeResource = URI.file(filenames[0]);
884 885 886
				}
			}

C
Christof Marti 已提交
887
			return <IQuickPickItem>{
888
				label: lang,
C
Christof Marti 已提交
889
				iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
B
Benjamin Pasero 已提交
890
				description
E
Erich Gamma 已提交
891 892
			};
		});
893

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

898
		// Offer action to configure via settings
C
Christof Marti 已提交
899 900
		let configureModeAssociations: IQuickPickItem;
		let configureModeSettings: IQuickPickItem;
901
		let galleryAction: Action;
M
Matt Bierner 已提交
902
		if (hasLanguageSupport && resource) {
B
Benjamin Pasero 已提交
903
			const ext = extname(resource) || basename(resource);
S
Sandeep Somavarapu 已提交
904 905 906 907 908 909

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

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

916
		// Offer to "Auto Detect"
C
Christof Marti 已提交
917
		const autoDetectMode: IQuickPickItem = {
918 919
			label: nls.localize('autoDetect', "Auto Detect")
		};
920

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

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

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

935 936
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
M
Matt Bierner 已提交
937 938 939
				if (resource) {
					this.configureFileAssociation(resource);
				}
940 941
				return;
			}
942

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

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

B
Benjamin Pasero 已提交
970 971 972 973 974 975 976
			// 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));
M
Matt Bierner 已提交
977 978
					}
				}
B
Benjamin Pasero 已提交
979 980 981 982 983 984 985 986 987
			} else {
				languageSelection = this.modeService.createByLanguageName(pick.label);
			}

			// Change mode
			if (typeof languageSelection !== 'undefined') {
				for (const textModel of models) {
					this.modelService.setMode(textModel, languageSelection);
				}
M
Matt Bierner 已提交
988
			}
E
Erich Gamma 已提交
989 990
		});
	}
991

992
	private configureFileAssociation(resource: URI): void {
B
Benjamin Pasero 已提交
993 994 995
		const extension = extname(resource);
		const base = basename(resource);
		const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(base);
996

997
		const languages = this.modeService.getRegisteredLanguageNames();
C
Christof Marti 已提交
998
		const picks: IQuickPickItem[] = languages.sort().map((lang, index) => {
999 1000
			const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());

C
Christof Marti 已提交
1001
			return <IQuickPickItem>{
1002 1003
				id,
				label: lang,
R
Rob Lourens 已提交
1004
				description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined
1005 1006 1007
			};
		});

1008
		setTimeout(() => {
B
Benjamin Pasero 已提交
1009
			this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) }).then(language => {
1010
				if (language) {
1011
					const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
1012 1013

					let associationKey: string;
B
Benjamin Pasero 已提交
1014
					if (extension && base[0] !== '.') {
1015 1016
						associationKey = `*${extension}`; // only use "*.ext" if the file path is in the form of <name>.<ext>
					} else {
B
Benjamin Pasero 已提交
1017
						associationKey = base; // otherwise use the basename (e.g. .gitignore, Dockerfile)
1018 1019
					}

1020 1021 1022 1023 1024 1025 1026
					// 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
M
Matt Bierner 已提交
1027
					const currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user) || Object.create(null);
1028 1029
					currentAssociations[associationKey] = language.id;

1030
					this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
1031
				}
1032
			});
1033
		}, 50 /* quick open is sensitive to being opened so soon after another */);
1034
	}
E
Erich Gamma 已提交
1035 1036
}

C
Christof Marti 已提交
1037
export interface IChangeEOLEntry extends IQuickPickItem {
E
Erich Gamma 已提交
1038 1039 1040
	eol: EndOfLineSequence;
}

I
isidor 已提交
1041
class ChangeIndentationAction extends Action {
I
isidor 已提交
1042

B
Benjamin Pasero 已提交
1043 1044
	static readonly ID = 'workbench.action.editor.changeIndentation';
	static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1045 1046 1047 1048

	constructor(
		actionId: string,
		actionLabel: string,
1049 1050
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
I
isidor 已提交
1051 1052 1053 1054
	) {
		super(actionId, actionLabel);
	}

J
Johannes Rieken 已提交
1055
	run(): Promise<any> {
1056 1057
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
1058
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
I
isidor 已提交
1059
		}
B
Benjamin Pasero 已提交
1060

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

C
Christof Marti 已提交
1065
		const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
1066 1067 1068 1069 1070 1071
			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 已提交
1072
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
1073 1074
			return {
				id: a.id,
B
Benjamin Pasero 已提交
1075
				label: a.label,
1076
				detail: Language.isDefaultVariant() ? undefined : a.alias,
1077
				run: () => {
1078
					activeTextEditorWidget.focus();
1079 1080
					a.run();
				}
B
Benjamin Pasero 已提交
1081 1082 1083
			};
		});

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

C
Christof Marti 已提交
1087
		return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
I
isidor 已提交
1088 1089 1090
	}
}

E
Erich Gamma 已提交
1091 1092
export class ChangeEOLAction extends Action {

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

	constructor(
		actionId: string,
		actionLabel: string,
1099 1100
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
E
Erich Gamma 已提交
1101 1102 1103 1104
	) {
		super(actionId, actionLabel);
	}

J
Johannes Rieken 已提交
1105
	run(): Promise<any> {
1106 1107
		const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
		if (!activeTextEditorWidget) {
C
Christof Marti 已提交
1108
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
E
Erich Gamma 已提交
1109 1110
		}

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

1115
		const textModel = activeTextEditorWidget.getModel();
E
Erich Gamma 已提交
1116

B
Benjamin Pasero 已提交
1117
		const EOLOptions: IChangeEOLEntry[] = [
1118 1119
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1120 1121
		];

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

C
Christof Marti 已提交
1124
		return this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }).then(eol => {
E
Erich Gamma 已提交
1125
			if (eol) {
1126
				const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
1127
				if (activeCodeEditor && activeCodeEditor.hasModel() && isWritableCodeEditor(activeCodeEditor)) {
1128
					const textModel = activeCodeEditor.getModel();
A
Alex Dima 已提交
1129
					textModel.pushEOL(eol.eol);
E
Erich Gamma 已提交
1130 1131 1132 1133 1134 1135 1136 1137
				}
			}
		});
	}
}

export class ChangeEncodingAction extends Action {

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

	constructor(
		actionId: string,
		actionLabel: string,
1144 1145 1146
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
B
Benjamin Pasero 已提交
1147 1148
		@IFileService private readonly fileService: IFileService,
		@ITextFileService private readonly textFileService: ITextFileService
E
Erich Gamma 已提交
1149 1150 1151 1152
	) {
		super(actionId, actionLabel);
	}

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

M
Matt Bierner 已提交
1158
		const activeControl = this.editorService.activeControl;
M
Matt Bierner 已提交
1159 1160 1161
		if (!activeControl) {
			return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
		}
M
Matt Bierner 已提交
1162
		const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input);
1163
		if (!encodingSupport) {
C
Christof Marti 已提交
1164
			return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
E
Erich Gamma 已提交
1165 1166
		}

C
Christof Marti 已提交
1167 1168
		let saveWithEncodingPick: IQuickPickItem;
		let reopenWithEncodingPick: IQuickPickItem;
1169
		if (Language.isDefaultVariant()) {
B
Benjamin Pasero 已提交
1170 1171 1172
			saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
			reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
		} else {
B
Benjamin Pasero 已提交
1173 1174
			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 已提交
1175
		}
E
Erich Gamma 已提交
1176

B
Benjamin Pasero 已提交
1177
		let pickActionPromise: Promise<IQuickPickItem>;
1178
		if (encodingSupport instanceof UntitledEditorInput) {
B
Benjamin Pasero 已提交
1179
			pickActionPromise = Promise.resolve(saveWithEncodingPick);
1180
		} else if (!isWritableBaseEditor(activeControl)) {
B
Benjamin Pasero 已提交
1181
			pickActionPromise = Promise.resolve(reopenWithEncodingPick);
E
Erich Gamma 已提交
1182
		} else {
C
Christof Marti 已提交
1183
			pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
E
Erich Gamma 已提交
1184 1185
		}

1186
		return pickActionPromise.then(action => {
E
Erich Gamma 已提交
1187
			if (!action) {
R
Rob Lourens 已提交
1188
				return undefined;
E
Erich Gamma 已提交
1189 1190
			}

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

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

B
Benjamin Pasero 已提交
1199
					return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
1200
				})
B
Benjamin Pasero 已提交
1201
				.then((guessedEncoding: string) => {
1202
					const isReopenWithEncoding = (action === reopenWithEncodingPick);
1203

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

M
Matt Bierner 已提交
1206
					let directMatchIndex: number | undefined;
M
Matt Bierner 已提交
1207
					let aliasMatchIndex: number | undefined;
1208 1209

					// All encodings are valid picks
C
Christof Marti 已提交
1210
					const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
						.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 已提交
1221 1222 1223 1224
							if (k === guessedEncoding && guessedEncoding !== configuredEncoding) {
								return false; // do not show encoding if it is the guessed encoding that does not match the configured
							}

1225 1226 1227 1228 1229 1230 1231 1232 1233
							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;
							}

1234
							return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
1235 1236
						});

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

B
Benjamin Pasero 已提交
1239 1240
					// 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 已提交
1241
						picks.unshift({ type: 'separator' });
B
Benjamin Pasero 已提交
1242
						picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
1243
					}
1244

C
Christof Marti 已提交
1245
					return this.quickInputService.pick(picks, {
1246
						placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
C
Christof Marti 已提交
1247
						activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
1248
					}).then(encoding => {
M
Matt Bierner 已提交
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258
						if (!encoding) {
							return;
						}
						const activeControl = this.editorService.activeControl;
						if (!activeControl) {
							return;
						}
						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
1259
						}
1260
					});
E
Erich Gamma 已提交
1261 1262 1263
				});
		});
	}
J
Johannes Rieken 已提交
1264
}