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

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

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

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

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

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

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

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

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

101
class StateChange {
A
Alex Dima 已提交
102
	_stateChangeBrand: void;
103

I
isidor 已提交
104
	indentation: boolean;
105 106 107 108
	selectionStatus: boolean;
	mode: boolean;
	encoding: boolean;
	EOL: boolean;
E
Erich Gamma 已提交
109
	tabFocusMode: boolean;
110
	screenReaderMode: boolean;
111
	metadata: boolean;
112 113 114 115 116 117 118 119

	constructor() {
		this.indentation = false;
		this.selectionStatus = false;
		this.mode = false;
		this.encoding = false;
		this.EOL = false;
		this.tabFocusMode = false;
120
		this.screenReaderMode = false;
121
		this.metadata = false;
122 123
	}

B
Benjamin Pasero 已提交
124
	combine(other: StateChange) {
125 126 127 128 129 130
		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;
131
		this.screenReaderMode = this.screenReaderMode || other.screenReaderMode;
132
		this.metadata = this.metadata || other.metadata;
133
	}
E
Erich Gamma 已提交
134 135
}

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

class State {
	private _selectionStatus: string;
B
Benjamin Pasero 已提交
149
	get selectionStatus(): string { return this._selectionStatus; }
150 151

	private _mode: string;
B
Benjamin Pasero 已提交
152
	get mode(): string { return this._mode; }
153 154

	private _encoding: string;
B
Benjamin Pasero 已提交
155
	get encoding(): string { return this._encoding; }
156 157

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

I
isidor 已提交
160
	private _indentation: string;
B
Benjamin Pasero 已提交
161
	get indentation(): string { return this._indentation; }
I
isidor 已提交
162

163
	private _tabFocusMode: boolean;
B
Benjamin Pasero 已提交
164
	get tabFocusMode(): boolean { return this._tabFocusMode; }
165

166
	private _screenReaderMode: boolean;
B
Benjamin Pasero 已提交
167
	get screenReaderMode(): boolean { return this._screenReaderMode; }
168

169
	private _metadata: string;
B
Benjamin Pasero 已提交
170
	get metadata(): string { return this._metadata; }
171

172 173 174 175 176 177
	constructor() {
		this._selectionStatus = null;
		this._mode = null;
		this._encoding = null;
		this._EOL = null;
		this._tabFocusMode = false;
178
		this._screenReaderMode = false;
179
		this._metadata = null;
180 181
	}

M
Matt Bierner 已提交
182
	update(update: StateDelta): StateChange | null {
B
Benjamin Pasero 已提交
183
		const e = new StateChange();
184 185 186 187 188 189 190 191 192
		let somethingChanged = false;

		if (typeof update.selectionStatus !== 'undefined') {
			if (this._selectionStatus !== update.selectionStatus) {
				this._selectionStatus = update.selectionStatus;
				somethingChanged = true;
				e.selectionStatus = true;
			}
		}
I
isidor 已提交
193
		if (typeof update.indentation !== 'undefined') {
194 195
			if (this._indentation !== update.indentation) {
				this._indentation = update.indentation;
I
isidor 已提交
196 197 198 199
				somethingChanged = true;
				e.indentation = true;
			}
		}
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
		if (typeof update.mode !== 'undefined') {
			if (this._mode !== update.mode) {
				this._mode = update.mode;
				somethingChanged = true;
				e.mode = true;
			}
		}
		if (typeof update.encoding !== 'undefined') {
			if (this._encoding !== update.encoding) {
				this._encoding = update.encoding;
				somethingChanged = true;
				e.encoding = true;
			}
		}
		if (typeof update.EOL !== 'undefined') {
			if (this._EOL !== update.EOL) {
				this._EOL = update.EOL;
				somethingChanged = true;
				e.EOL = true;
			}
		}
		if (typeof update.tabFocusMode !== 'undefined') {
			if (this._tabFocusMode !== update.tabFocusMode) {
				this._tabFocusMode = update.tabFocusMode;
				somethingChanged = true;
				e.tabFocusMode = true;
			}
		}
228 229 230 231 232 233 234
		if (typeof update.screenReaderMode !== 'undefined') {
			if (this._screenReaderMode !== update.screenReaderMode) {
				this._screenReaderMode = update.screenReaderMode;
				somethingChanged = true;
				e.screenReaderMode = true;
			}
		}
235 236 237 238 239 240 241
		if (typeof update.metadata !== 'undefined') {
			if (this._metadata !== update.metadata) {
				this._metadata = update.metadata;
				somethingChanged = true;
				e.metadata = true;
			}
		}
242 243 244 245 246 247 248 249

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

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

260
function setDisplay(el: HTMLElement, desiredValue: string): void {
A
Alex Dima 已提交
261 262 263 264
	if (el.style.display !== desiredValue) {
		el.style.display = desiredValue;
	}
}
265
function show(el: HTMLElement): void {
266
	setDisplay(el, '');
A
Alex Dima 已提交
267
}
268
function hide(el: HTMLElement): void {
269
	setDisplay(el, 'none');
A
Alex Dima 已提交
270 271
}

272
export class EditorStatus implements IStatusbarItem {
273
	private state: State;
274 275
	private element: HTMLElement;
	private tabFocusModeElement: HTMLElement;
276
	private screenRedearModeElement: HTMLElement;
I
isidor 已提交
277
	private indentationElement: HTMLElement;
278 279 280 281
	private selectionElement: HTMLElement;
	private encodingElement: HTMLElement;
	private eolElement: HTMLElement;
	private modeElement: HTMLElement;
282
	private metadataElement: HTMLElement;
E
Erich Gamma 已提交
283
	private toDispose: IDisposable[];
284
	private activeEditorListeners: IDisposable[];
M
Matt Bierner 已提交
285 286 287
	private delayedRender: IDisposable | null;
	private toRender: StateChange | null;
	private screenReaderNotification: INotificationHandle | null;
E
Erich Gamma 已提交
288

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

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

307
		this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
308 309 310
		this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode");
		this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick();
		this.tabFocusModeElement.textContent = nlsTabFocusMode;
311
		hide(this.tabFocusModeElement);
312

313 314
		this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info'));
		this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
315
		this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle;
316
		this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();
317 318
		hide(this.screenRedearModeElement);

319 320 321
		this.selectionElement = append(this.element, $('a.editor-status-selection'));
		this.selectionElement.title = nls.localize('gotoLine', "Go to Line");
		this.selectionElement.onclick = () => this.onSelectionClick();
322
		hide(this.selectionElement);
323

324
		this.indentationElement = append(this.element, $('a.editor-status-indentation'));
325
		this.indentationElement.title = nls.localize('selectIndentation', "Select Indentation");
326 327 328
		this.indentationElement.onclick = () => this.onIndentationClick();
		hide(this.indentationElement);

329 330 331
		this.encodingElement = append(this.element, $('a.editor-status-encoding'));
		this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding");
		this.encodingElement.onclick = () => this.onEncodingClick();
332
		hide(this.encodingElement);
333 334 335 336

		this.eolElement = append(this.element, $('a.editor-status-eol'));
		this.eolElement.title = nls.localize('selectEOL', "Select End of Line Sequence");
		this.eolElement.onclick = () => this.onEOLClick();
337
		hide(this.eolElement);
338 339 340 341

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

344 345 346 347
		this.metadataElement = append(this.element, $('span.editor-status-metadata'));
		this.metadataElement.title = nls.localize('fileInfo', "File Information");
		hide(this.metadataElement);

348 349 350
		this.delayedRender = null;
		this.toRender = null;

351
		this.toDispose.push(
352 353 354 355 356 357 358 359
			{
				dispose: () => {
					if (this.delayedRender) {
						this.delayedRender.dispose();
						this.delayedRender = null;
					}
				}
			},
360
			this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
361 362
			this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
			this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
363
			TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
364
		);
E
Erich Gamma 已提交
365

J
Joao Moreno 已提交
366
		return combinedDisposable(this.toDispose);
367 368
	}

369
	private updateState(update: StateDelta): void {
B
Benjamin Pasero 已提交
370
		const changed = this.state.update(update);
371 372 373
		if (!changed) {
			// Nothing really changed
			return;
E
Erich Gamma 已提交
374 375
		}

376 377 378 379
		if (!this.toRender) {
			this.toRender = changed;
			this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => {
				this.delayedRender = null;
B
Benjamin Pasero 已提交
380
				const toRender = this.toRender;
381
				this.toRender = null;
M
Matt Bierner 已提交
382 383 384
				if (toRender) {
					this._renderNow(toRender);
				}
385 386 387 388 389 390
			});
		} else {
			this.toRender.combine(changed);
		}
	}

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

400 401 402 403 404 405 406 407
		if (changed.screenReaderMode) {
			if (this.state.screenReaderMode && this.state.screenReaderMode === true) {
				show(this.screenRedearModeElement);
			} else {
				hide(this.screenRedearModeElement);
			}
		}

I
isidor 已提交
408 409 410 411 412 413 414 415 416
		if (changed.indentation) {
			if (this.state.indentation) {
				this.indentationElement.textContent = this.state.indentation;
				show(this.indentationElement);
			} else {
				hide(this.indentationElement);
			}
		}

417
		if (changed.selectionStatus) {
418
			if (this.state.selectionStatus && !this.state.screenReaderMode) {
419 420 421 422 423
				this.selectionElement.textContent = this.state.selectionStatus;
				show(this.selectionElement);
			} else {
				hide(this.selectionElement);
			}
E
Erich Gamma 已提交
424 425
		}

426 427 428 429 430 431 432
		if (changed.encoding) {
			if (this.state.encoding) {
				this.encodingElement.textContent = this.state.encoding;
				show(this.encodingElement);
			} else {
				hide(this.encodingElement);
			}
E
Erich Gamma 已提交
433 434
		}

435 436 437 438 439 440 441
		if (changed.EOL) {
			if (this.state.EOL) {
				this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF;
				show(this.eolElement);
			} else {
				hide(this.eolElement);
			}
E
Erich Gamma 已提交
442 443
		}

444 445 446 447 448 449 450 451
		if (changed.mode) {
			if (this.state.mode) {
				this.modeElement.textContent = this.state.mode;
				show(this.modeElement);
			} else {
				hide(this.modeElement);
			}
		}
452 453 454 455 456 457 458 459 460

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

M
Matt Bierner 已提交
463
	private getSelectionLabel(info: IEditorSelectionStatus): string | undefined {
E
Erich Gamma 已提交
464
		if (!info || !info.selections) {
M
Matt Bierner 已提交
465
			return undefined;
E
Erich Gamma 已提交
466 467 468 469
		}

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

			return strings.format(nlsSingleSelection, info.selections[0].positionLineNumber, info.selections[0].positionColumn);
E
Erich Gamma 已提交
474
		}
B
Benjamin Pasero 已提交
475 476 477 478 479 480 481 482 483

		if (info.charactersSelected) {
			return strings.format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected);
		}

		if (info.selections.length > 0) {
			return strings.format(nlsMultiSelection, info.selections.length);
		}

M
Matt Bierner 已提交
484
		return undefined;
E
Erich Gamma 已提交
485 486
	}

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

490
		action.run();
491 492 493
		action.dispose();
	}

I
isidor 已提交
494 495
	private onIndentationClick(): void {
		const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
496
		action.run();
I
isidor 已提交
497 498 499
		action.dispose();
	}

500
	private onScreenReaderModeClick(): void {
501 502 503
		if (!this.screenReaderNotification) {
			this.screenReaderNotification = this.notificationService.prompt(
				Severity.Info,
A
Alex Dima 已提交
504
				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)"),
505 506 507 508 509 510 511 512 513 514
				[{
					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 已提交
515 516
				}],
				{ sticky: true }
517
			);
B
Benjamin Pasero 已提交
518

J
Joao Moreno 已提交
519
			Event.once(this.screenReaderNotification.onDidClose)(() => {
B
Benjamin Pasero 已提交
520 521
				this.screenReaderNotification = null;
			});
522
		}
523 524
	}

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

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

532
		action.run();
533 534 535 536
		action.dispose();
	}

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

539
		action.run();
540 541 542 543
		action.dispose();
	}

	private onTabFocusModeClick(): void {
544
		TabFocus.setTabFocusMode(false);
545 546
	}

547 548
	private updateStatusBar(): void {
		const activeControl = this.editorService.activeControl;
M
Matt Bierner 已提交
549
		const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) || undefined : undefined;
550 551

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

560 561 562 563
		// Dispose old active editor listeners
		dispose(this.activeEditorListeners);

		// Attach new listeners to active editor
564
		if (activeCodeEditor) {
565

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

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

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

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

M
Matt Bierner 已提交
587
				const selections = activeCodeEditor.getSelections();
588 589
				for (const change of e.changes) {
					if (selections.some(selection => Range.areIntersecting(selection, change.range))) {
590 591 592 593
						this.onSelectionChange(activeCodeEditor);
						break;
					}
				}
594 595 596
			}));

			// Hook Listener for content options changes
597 598
			this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
				this.onIndentationChange(activeCodeEditor);
599
			}));
E
Erich Gamma 已提交
600
		}
601 602

		// Handle binary editors
603
		else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
604
			const binaryEditors: BaseBinaryResourceEditor[] = [];
605 606
			if (activeControl instanceof BinaryResourceDiffEditor) {
				const details = activeControl.getDetailsEditor();
607 608 609 610
				if (details instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(details);
				}

611
				const master = activeControl.getMasterEditor();
612 613 614
				if (master instanceof BaseBinaryResourceEditor) {
					binaryEditors.push(master);
				}
615
			} else {
616
				binaryEditors.push(activeControl);
617 618 619 620
			}

			binaryEditors.forEach(editor => {
				this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
621
					this.onMetadataChange(activeControl);
622
				}));
623 624 625 626

				this.activeEditorListeners.push(editor.onDidOpenInPlace(() => {
					this.updateStatusBar();
				}));
627
			});
628
		}
629
	}
E
Erich Gamma 已提交
630

M
Matt Bierner 已提交
631 632
	private onModeChange(editorWidget: ICodeEditor | undefined): void {
		let info: StateDelta = { mode: undefined };
E
Erich Gamma 已提交
633 634

		// We only support text based editors
635
		if (editorWidget) {
A
Alex Dima 已提交
636
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
637 638
			if (textModel) {
				// Compute mode
A
Alex Dima 已提交
639
				const modeId = textModel.getLanguageIdentifier().language;
M
Matt Bierner 已提交
640
				info = { mode: this.modeService.getLanguageName(modeId) || undefined };
E
Erich Gamma 已提交
641 642 643 644 645 646
			}
		}

		this.updateState(info);
	}

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

650
		if (editorWidget) {
A
Alex Dima 已提交
651
			const model = editorWidget.getModel();
652 653 654 655 656 657 658
			if (model) {
				const modelOpts = model.getOptions();
				update.indentation = (
					modelOpts.insertSpaces
						? nls.localize('spacesSize', "Spaces: {0}", modelOpts.tabSize)
						: nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize)
				);
659
			}
I
isidor 已提交
660
		}
661

I
isidor 已提交
662 663 664
		this.updateState(update);
	}

665
	private onMetadataChange(editor: IBaseEditor): void {
M
Matt Bierner 已提交
666
		const update: StateDelta = { metadata: undefined };
667 668

		if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) {
M
Matt Bierner 已提交
669
			update.metadata = editor.getMetadata() || undefined;
670 671 672 673 674
		}

		this.updateState(update);
	}

675 676
	private _promptedScreenReader: boolean = false;

M
Matt Bierner 已提交
677
	private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
678 679 680 681
		let screenReaderMode = false;

		// We only support text based editors
		if (editorWidget) {
682 683
			const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled);
			if (screenReaderDetected) {
684
				const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
685 686 687 688 689 690 691 692 693 694
				if (screenReaderConfiguration === 'auto') {
					// show explanation
					if (!this._promptedScreenReader) {
						this._promptedScreenReader = true;
						setTimeout(() => {
							this.onScreenReaderModeClick();
						}, 100);
					}
				}
			}
695 696 697 698

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

699 700
		if (screenReaderMode === false && this.screenReaderNotification) {
			this.screenReaderNotification.close();
701 702
		}

703 704 705
		this.updateState({ screenReaderMode: screenReaderMode });
	}

M
Matt Bierner 已提交
706
	private onSelectionChange(editorWidget: ICodeEditor | undefined): void {
B
Benjamin Pasero 已提交
707
		const info: IEditorSelectionStatus = {};
E
Erich Gamma 已提交
708 709

		// We only support text based editors
710
		if (editorWidget) {
E
Erich Gamma 已提交
711 712 713 714 715 716

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

			// Compute selection length
			info.charactersSelected = 0;
A
Alex Dima 已提交
717
			const textModel = editorWidget.getModel();
E
Erich Gamma 已提交
718
			if (textModel) {
719
				info.selections.forEach(selection => {
M
Matt Bierner 已提交
720
					info.charactersSelected! += textModel.getValueLengthInRange(selection);
E
Erich Gamma 已提交
721 722 723 724 725
				});
			}

			// Compute the visible column for one selection. This will properly handle tabs and their configured widths
			if (info.selections.length === 1) {
B
Benjamin Pasero 已提交
726
				const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition());
E
Erich Gamma 已提交
727

728 729 730 731 732 733 734
				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 已提交
735 736 737 738 739

				info.selections[0] = selectionClone;
			}
		}

740
		this.updateState({ selectionStatus: this.getSelectionLabel(info) });
E
Erich Gamma 已提交
741 742
	}

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

A
Alex Dima 已提交
746 747
		if (editorWidget && !editorWidget.getConfiguration().readOnly) {
			const codeEditorModel = editorWidget.getModel();
I
isidor 已提交
748 749 750
			if (codeEditorModel) {
				info.EOL = codeEditorModel.getEOL();
			}
E
Erich Gamma 已提交
751 752 753 754 755
		}

		this.updateState(info);
	}

756
	private onEncodingChange(e?: IBaseEditor): void {
E
Erich Gamma 已提交
757 758 759 760
		if (e && !this.isActiveEditor(e)) {
			return;
		}

M
Matt Bierner 已提交
761
		const info: StateDelta = { encoding: undefined };
E
Erich Gamma 已提交
762 763

		// We only support text based editors
764
		if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
765 766
			const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
			if (encodingSupport) {
B
Benjamin Pasero 已提交
767 768
				const rawEncoding = encodingSupport.getEncoding();
				const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding];
E
Erich Gamma 已提交
769 770 771 772 773 774 775 776 777 778 779
				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);
	}

780
	private onResourceEncodingChange(resource: uri): void {
781 782 783
		const activeControl = this.editorService.activeControl;
		if (activeControl) {
			const activeResource = toResource(activeControl.input, { supportSideBySide: true });
B
Benjamin Pasero 已提交
784
			if (activeResource && activeResource.toString() === resource.toString()) {
785
				return this.onEncodingChange(<IBaseEditor>activeControl); // only update if the encoding changed for the active resource
786 787
			}
		}
E
Erich Gamma 已提交
788 789
	}

790
	private onTabFocusModeChange(): void {
B
Benjamin Pasero 已提交
791
		const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() };
E
Erich Gamma 已提交
792 793 794 795

		this.updateState(info);
	}

796 797
	private isActiveEditor(control: IBaseEditor): boolean {
		const activeControl = this.editorService.activeControl;
E
Erich Gamma 已提交
798

799
		return !!activeControl && activeControl === control;
E
Erich Gamma 已提交
800 801 802
	}
}

M
Matt Bierner 已提交
803
function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean {
A
Alex Dima 已提交
804
	if (!codeEditor) {
S
Sandeep Somavarapu 已提交
805 806
		return false;
	}
A
Alex Dima 已提交
807 808 809
	const config = codeEditor.getConfiguration();
	return (!config.readOnly);
}
S
Sandeep Somavarapu 已提交
810

A
Alex Dima 已提交
811
function isWritableBaseEditor(e: IBaseEditor): boolean {
M
Matt Bierner 已提交
812
	return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined);
E
Erich Gamma 已提交
813 814
}

815 816
export class ShowLanguageExtensionsAction extends Action {

817
	static readonly ID = 'workbench.action.showLanguageExtensions';
818 819

	constructor(
820
		private fileExtension: string,
821
		@ICommandService private readonly commandService: ICommandService,
822 823
		@IExtensionGalleryService galleryService: IExtensionGalleryService
	) {
824 825
		super(ShowLanguageExtensionsAction.ID, nls.localize('showLanguageExtensions', "Search Marketplace Extensions for '{0}'...", fileExtension));

826 827 828
		this.enabled = galleryService.isEnabled();
	}

J
Johannes Rieken 已提交
829
	run(): Promise<void> {
R
Rob Lourens 已提交
830
		return this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension).then(() => undefined);
831 832 833
	}
}

E
Erich Gamma 已提交
834 835
export class ChangeModeAction extends Action {

B
Benjamin Pasero 已提交
836 837
	static readonly ID = 'workbench.action.editor.changeLanguageMode';
	static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
E
Erich Gamma 已提交
838 839 840 841

	constructor(
		actionId: string,
		actionLabel: string,
842 843 844 845 846 847 848 849
		@IModeService private readonly modeService: IModeService,
		@IModelService private readonly modelService: IModelService,
		@IEditorService private readonly editorService: IEditorService,
		@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@IPreferencesService private readonly preferencesService: IPreferencesService,
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService
E
Erich Gamma 已提交
850 851 852 853
	) {
		super(actionId, actionLabel);
	}

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

860
		const textModel = activeTextEditorWidget.getModel();
B
Benjamin Pasero 已提交
861
		const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
B
Benjamin Pasero 已提交
862 863

		let hasLanguageSupport = !!resource;
864
		if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
B
Benjamin Pasero 已提交
865
			hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
866
		}
E
Erich Gamma 已提交
867 868 869

		// Compute mode
		let currentModeId: string;
870
		let modeId: string;
871
		if (textModel) {
872
			modeId = textModel.getLanguageIdentifier().language;
A
Alex Dima 已提交
873
			currentModeId = this.modeService.getLanguageName(modeId);
E
Erich Gamma 已提交
874 875 876
		}

		// All languages are valid picks
877
		const languages = this.modeService.getRegisteredLanguageNames();
C
Christof Marti 已提交
878
		const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
B
Benjamin Pasero 已提交
879 880 881 882 883 884
			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()));
			}
885

886
			// construct a fake resource to be able to show nice icons if any
M
Matt Bierner 已提交
887
			let fakeResource: uri | undefined;
888 889 890 891 892 893 894 895 896 897
			const extensions = this.modeService.getExtensions(lang);
			if (extensions && extensions.length) {
				fakeResource = uri.file(extensions[0]);
			} else {
				const filenames = this.modeService.getFilenames(lang);
				if (filenames && filenames.length) {
					fakeResource = uri.file(filenames[0]);
				}
			}

C
Christof Marti 已提交
898
			return <IQuickPickItem>{
899
				label: lang,
C
Christof Marti 已提交
900
				iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
B
Benjamin Pasero 已提交
901
				description
E
Erich Gamma 已提交
902 903
			};
		});
904

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

909
		// Offer action to configure via settings
C
Christof Marti 已提交
910 911
		let configureModeAssociations: IQuickPickItem;
		let configureModeSettings: IQuickPickItem;
912
		let galleryAction: Action;
B
Benjamin Pasero 已提交
913
		if (hasLanguageSupport) {
B
Benjamin Pasero 已提交
914
			const ext = extname(resource) || basename(resource);
S
Sandeep Somavarapu 已提交
915 916 917 918 919 920

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

921 922
			configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
			picks.unshift(configureModeSettings);
923
			configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
924 925
			picks.unshift(configureModeAssociations);
		}
E
Erich Gamma 已提交
926

927
		// Offer to "Auto Detect"
C
Christof Marti 已提交
928
		const autoDetectMode: IQuickPickItem = {
929 930
			label: nls.localize('autoDetect', "Auto Detect")
		};
931

B
Benjamin Pasero 已提交
932
		if (hasLanguageSupport) {
933 934
			picks.unshift(autoDetectMode);
		}
935

C
Christof Marti 已提交
936
		return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
937 938 939
			if (!pick) {
				return;
			}
B
Benjamin Pasero 已提交
940

941 942 943 944
			if (pick === galleryAction) {
				galleryAction.run();
				return;
			}
B
Benjamin Pasero 已提交
945

946 947
			// User decided to permanently configure associations, return right after
			if (pick === configureModeAssociations) {
948
				this.configureFileAssociation(resource);
949 950
				return;
			}
951

952 953
			// User decided to configure settings for current language
			if (pick === configureModeSettings) {
954
				this.preferencesService.configureSettingsForLanguage(modeId);
955 956 957
				return;
			}

958
			// Change mode for active editor
B
Benjamin Pasero 已提交
959
			const activeEditor = this.editorService.activeEditor;
960
			const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
A
Alex Dima 已提交
961
			const models: ITextModel[] = [];
962 963
			if (isCodeEditor(activeTextEditorWidget)) {
				const codeEditorModel = activeTextEditorWidget.getModel();
964 965
				if (codeEditorModel) {
					models.push(codeEditorModel);
966
				}
967 968
			} else if (isDiffEditor(activeTextEditorWidget)) {
				const diffEditorModel = activeTextEditorWidget.getModel();
969 970 971 972 973 974 975
				if (diffEditorModel) {
					if (diffEditorModel.original) {
						models.push(diffEditorModel.original);
					}
					if (diffEditorModel.modified) {
						models.push(diffEditorModel.modified);
					}
E
Erich Gamma 已提交
976
				}
977
			}
978

979
			// Find mode
A
Alex Dima 已提交
980
			let languageSelection: ILanguageSelection;
981
			if (pick === autoDetectMode) {
A
Alex Dima 已提交
982
				languageSelection = this.modeService.createByFilepathOrFirstLine(toResource(activeEditor, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
983
			} else {
A
Alex Dima 已提交
984
				languageSelection = this.modeService.createByLanguageName(pick.label);
E
Erich Gamma 已提交
985
			}
986 987 988

			// Change mode
			models.forEach(textModel => {
A
Alex Dima 已提交
989
				this.modelService.setMode(textModel, languageSelection);
990
			});
E
Erich Gamma 已提交
991 992
		});
	}
993 994

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

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

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

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

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

1022 1023 1024 1025 1026 1027 1028
					// If the association is already being made in the workspace, make sure to target workspace settings
					let target = ConfigurationTarget.USER;
					if (fileAssociationsConfig.workspace && !!fileAssociationsConfig.workspace[associationKey]) {
						target = ConfigurationTarget.WORKSPACE;
					}

					// Make sure to write into the value of the target and not the merged value from USER and WORKSPACE config
J
Johannes Rieken 已提交
1029
					let currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user);
1030 1031 1032
					if (!currentAssociations) {
						currentAssociations = Object.create(null);
					}
1033

1034 1035
					currentAssociations[associationKey] = language.id;

1036
					this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
1037
				}
1038
			});
1039
		}, 50 /* quick open is sensitive to being opened so soon after another */);
1040
	}
E
Erich Gamma 已提交
1041 1042
}

C
Christof Marti 已提交
1043
export interface IChangeEOLEntry extends IQuickPickItem {
E
Erich Gamma 已提交
1044 1045 1046
	eol: EndOfLineSequence;
}

I
isidor 已提交
1047
class ChangeIndentationAction extends Action {
I
isidor 已提交
1048

B
Benjamin Pasero 已提交
1049 1050
	static readonly ID = 'workbench.action.editor.changeIndentation';
	static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
I
isidor 已提交
1051 1052 1053 1054

	constructor(
		actionId: string,
		actionLabel: string,
1055 1056
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
I
isidor 已提交
1057 1058 1059 1060
	) {
		super(actionId, actionLabel);
	}

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

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

C
Christof Marti 已提交
1071
		const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
1072 1073 1074 1075 1076 1077
			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 已提交
1078
		].map((a: IEditorAction) => {
B
Benjamin Pasero 已提交
1079 1080
			return {
				id: a.id,
B
Benjamin Pasero 已提交
1081
				label: a.label,
M
Matt Bierner 已提交
1082
				detail: (language === LANGUAGE_DEFAULT) ? undefined : a.alias,
1083
				run: () => {
1084
					activeTextEditorWidget.focus();
1085 1086
					a.run();
				}
B
Benjamin Pasero 已提交
1087 1088 1089
			};
		});

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

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

E
Erich Gamma 已提交
1097 1098
export class ChangeEOLAction extends Action {

B
Benjamin Pasero 已提交
1099 1100
	static readonly ID = 'workbench.action.editor.changeEOL';
	static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
E
Erich Gamma 已提交
1101 1102 1103 1104

	constructor(
		actionId: string,
		actionLabel: string,
1105 1106
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService
E
Erich Gamma 已提交
1107 1108 1109 1110
	) {
		super(actionId, actionLabel);
	}

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

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

1121
		const textModel = activeTextEditorWidget.getModel();
E
Erich Gamma 已提交
1122

B
Benjamin Pasero 已提交
1123
		const EOLOptions: IChangeEOLEntry[] = [
1124 1125
			{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
			{ label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF },
E
Erich Gamma 已提交
1126 1127
		];

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

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

export class ChangeEncodingAction extends Action {

B
Benjamin Pasero 已提交
1144 1145
	static readonly ID = 'workbench.action.editor.changeEncoding';
	static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
E
Erich Gamma 已提交
1146 1147 1148 1149

	constructor(
		actionId: string,
		actionLabel: string,
1150 1151 1152 1153
		@IEditorService private readonly editorService: IEditorService,
		@IQuickInputService private readonly quickInputService: IQuickInputService,
		@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
		@IFileService private readonly fileService: IFileService
E
Erich Gamma 已提交
1154 1155 1156 1157
	) {
		super(actionId, actionLabel);
	}

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

B
Benjamin Pasero 已提交
1163
		let activeControl = this.editorService.activeControl;
1164
		let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
1165
		if (!encodingSupport) {
C
Christof Marti 已提交
1166
			return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
E
Erich Gamma 已提交
1167 1168
		}

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

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

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

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

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

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

1206
					const configuredEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding');
1207

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

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

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

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

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

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

C
Christof Marti 已提交
1247
					return this.quickInputService.pick(picks, {
1248
						placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
C
Christof Marti 已提交
1249
						activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
1250 1251
					}).then(encoding => {
						if (encoding) {
1252 1253
							activeControl = this.editorService.activeControl;
							encodingSupport = toEditorWithEncodingSupport(activeControl.input);
1254 1255 1256
							if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
								encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
							}
1257
						}
1258
					});
E
Erich Gamma 已提交
1259 1260 1261
				});
		});
	}
J
Johannes Rieken 已提交
1262
}