indentation.ts 23.1 KB
Newer Older
I
isidor 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

A
Alex Dima 已提交
6
import * as nls from 'vs/nls';
R
rebornix 已提交
7
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
R
rebornix 已提交
8
import * as strings from 'vs/base/common/strings';
A
Alex Dima 已提交
9 10 11 12
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import { EditOperation } from 'vs/editor/common/core/editOperation';
P
Peng Lyu 已提交
13
import { Range, IRange } from 'vs/editor/common/core/range';
14
import { Selection } from 'vs/editor/common/core/selection';
A
Alex Dima 已提交
15 16
import { ICommand, ICursorStateComputerData, IEditOperationBuilder, IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
P
Peng Lyu 已提交
17
import { IIdentifiedSingleEditOperation, ITextModel, EndOfLineSequence } from 'vs/editor/common/model';
A
Alex Dima 已提交
18
import { TextModel } from 'vs/editor/common/model/textModel';
A
Alex Dima 已提交
19
import { StandardTokenType, TextEdit } from 'vs/editor/common/modes';
R
rebornix 已提交
20
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
21
import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
A
Alex Dima 已提交
22 23
import { IModelService } from 'vs/editor/common/services/modelService';
import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils';
C
Christof Marti 已提交
24
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
R
rebornix 已提交
25

A
Alex Dima 已提交
26
export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] {
R
rebornix 已提交
27 28
	if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
		// Model is empty
P
Peng Lyu 已提交
29
		return [];
R
rebornix 已提交
30 31 32 33
	}

	let indentationRules = LanguageConfigurationRegistry.getIndentationRules(model.getLanguageIdentifier().id);
	if (!indentationRules) {
P
Peng Lyu 已提交
34
		return [];
R
rebornix 已提交
35 36 37 38
	}

	endLineNumber = Math.min(endLineNumber, model.getLineCount());

R
rebornix 已提交
39
	// Skip `unIndentedLinePattern` lines
R
rebornix 已提交
40 41 42 43 44
	while (startLineNumber <= endLineNumber) {
		if (!indentationRules.unIndentedLinePattern) {
			break;
		}

R
rebornix 已提交
45 46
		let text = model.getLineContent(startLineNumber);
		if (!indentationRules.unIndentedLinePattern.test(text)) {
R
rebornix 已提交
47 48 49 50 51 52 53
			break;
		}

		startLineNumber++;
	}

	if (startLineNumber > endLineNumber - 1) {
P
Peng Lyu 已提交
54
		return [];
R
rebornix 已提交
55 56
	}

A
Alex Dima 已提交
57 58 59 60 61 62 63 64 65
	const { tabSize, indentSize, insertSpaces } = model.getOptions();
	const shiftIndent = (indentation: string, count?: number) => {
		count = count || 1;
		return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
	};
	const unshiftIndent = (indentation: string, count?: number) => {
		count = count || 1;
		return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces);
	};
M
Matt Bierner 已提交
66
	let indentEdits: IIdentifiedSingleEditOperation[] = [];
R
rebornix 已提交
67 68

	// indentation being passed to lines below
R
rebornix 已提交
69 70
	let globalIndent: string;

R
rebornix 已提交
71 72 73 74
	// Calculate indentation for the first line
	// If there is no passed-in indentation, we use the indentation of the first line as base.
	let currentLineText = model.getLineContent(startLineNumber);
	let adjustedLineContent = currentLineText;
R
rebornix 已提交
75
	if (inheritedIndent !== undefined && inheritedIndent !== null) {
R
rebornix 已提交
76 77
		globalIndent = inheritedIndent;
		let oldIndentation = strings.getLeadingWhitespace(currentLineText);
R
rebornix 已提交
78

R
rebornix 已提交
79
		adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
R
rebornix 已提交
80
		if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
81
			globalIndent = unshiftIndent(globalIndent);
R
rebornix 已提交
82
			adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length);
R
rebornix 已提交
83 84

		}
R
rebornix 已提交
85
		if (currentLineText !== adjustedLineContent) {
A
Alex Dima 已提交
86
			indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces)));
R
rebornix 已提交
87 88
		}
	} else {
R
rebornix 已提交
89
		globalIndent = strings.getLeadingWhitespace(currentLineText);
R
rebornix 已提交
90 91
	}

R
rebornix 已提交
92 93 94
	// idealIndentForNextLine doesn't equal globalIndent when there is a line matching `indentNextLinePattern`.
	let idealIndentForNextLine: string = globalIndent;

R
rebornix 已提交
95
	if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
96 97
		idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
		globalIndent = shiftIndent(globalIndent);
R
rebornix 已提交
98 99
	}
	else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
100
		idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
R
rebornix 已提交
101 102 103 104
	}

	startLineNumber++;

R
rebornix 已提交
105
	// Calculate indentation adjustment for all following lines
R
rebornix 已提交
106
	for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
R
rebornix 已提交
107 108 109
		let text = model.getLineContent(lineNumber);
		let oldIndentation = strings.getLeadingWhitespace(text);
		let adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length);
R
rebornix 已提交
110 111

		if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
112 113
			idealIndentForNextLine = unshiftIndent(idealIndentForNextLine);
			globalIndent = unshiftIndent(globalIndent);
R
rebornix 已提交
114 115 116
		}

		if (oldIndentation !== idealIndentForNextLine) {
A
Alex Dima 已提交
117
			indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces)));
R
rebornix 已提交
118 119 120
		}

		// calculate idealIndentForNextLine
121 122 123 124 125
		if (indentationRules.unIndentedLinePattern && indentationRules.unIndentedLinePattern.test(text)) {
			// In reindent phase, if the line matches `unIndentedLinePattern` we inherit indentation from above lines
			// but don't change globalIndent and idealIndentForNextLine.
			continue;
		} else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
126
			globalIndent = shiftIndent(globalIndent);
R
rebornix 已提交
127
			idealIndentForNextLine = globalIndent;
128
		} else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) {
A
Alex Dima 已提交
129
			idealIndentForNextLine = shiftIndent(idealIndentForNextLine);
R
rebornix 已提交
130 131 132 133 134 135 136
		} else {
			idealIndentForNextLine = globalIndent;
		}
	}

	return indentEdits;
}
I
isidor 已提交
137

A
Alex Dima 已提交
138
export class IndentationToSpacesAction extends EditorAction {
M
Matt Bierner 已提交
139
	public static readonly ID = 'editor.action.indentationToSpaces';
I
isidor 已提交
140

A
Alex Dima 已提交
141
	constructor() {
142 143 144 145
		super({
			id: IndentationToSpacesAction.ID,
			label: nls.localize('indentationToSpaces', "Convert Indentation to Spaces"),
			alias: 'Convert Indentation to Spaces',
146
			precondition: EditorContextKeys.writable
147
		});
I
isidor 已提交
148 149
	}

150
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
A
Alex Dima 已提交
151
		let model = editor.getModel();
152 153 154 155
		if (!model) {
			return;
		}
		let modelOpts = model.getOptions();
P
Peng Lyu 已提交
156 157 158 159 160
		let selection = editor.getSelection();
		if (!selection) {
			return;
		}
		const command = new IndentationToSpacesCommand(selection, modelOpts.tabSize);
A
Alex Dima 已提交
161 162

		editor.pushUndoStop();
A
Alex Dima 已提交
163
		editor.executeCommands(this.id, [command]);
A
Alex Dima 已提交
164 165
		editor.pushUndoStop();

166 167 168
		model.updateOptions({
			insertSpaces: true
		});
I
isidor 已提交
169 170 171
	}
}

A
Alex Dima 已提交
172
export class IndentationToTabsAction extends EditorAction {
M
Matt Bierner 已提交
173
	public static readonly ID = 'editor.action.indentationToTabs';
I
isidor 已提交
174

A
Alex Dima 已提交
175
	constructor() {
176 177 178 179
		super({
			id: IndentationToTabsAction.ID,
			label: nls.localize('indentationToTabs', "Convert Indentation to Tabs"),
			alias: 'Convert Indentation to Tabs',
180
			precondition: EditorContextKeys.writable
181
		});
I
isidor 已提交
182 183
	}

184
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
A
Alex Dima 已提交
185
		let model = editor.getModel();
186 187 188 189
		if (!model) {
			return;
		}
		let modelOpts = model.getOptions();
P
Peng Lyu 已提交
190 191 192 193 194
		let selection = editor.getSelection();
		if (!selection) {
			return;
		}
		const command = new IndentationToTabsCommand(selection, modelOpts.tabSize);
A
Alex Dima 已提交
195 196

		editor.pushUndoStop();
A
Alex Dima 已提交
197
		editor.executeCommands(this.id, [command]);
A
Alex Dima 已提交
198 199
		editor.pushUndoStop();

200 201 202
		model.updateOptions({
			insertSpaces: false
		});
I
isidor 已提交
203 204 205
	}
}

A
Alex Dima 已提交
206
export class ChangeIndentationSizeAction extends EditorAction {
207

208
	constructor(private readonly insertSpaces: boolean, opts: IActionOptions) {
209
		super(opts);
210 211
	}

212
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
C
Christof Marti 已提交
213
		const quickInputService = accessor.get(IQuickInputService);
A
Alex Dima 已提交
214 215 216
		const modelService = accessor.get(IModelService);

		let model = editor.getModel();
217
		if (!model) {
218
			return;
219
		}
220

221
		let creationOpts = modelService.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget);
222 223 224
		const picks = [1, 2, 3, 4, 5, 6, 7, 8].map(n => ({
			id: n.toString(),
			label: n.toString(),
I
isidor 已提交
225
			// add description for tabSize value set in the configuration
P
Peng Lyu 已提交
226
			description: n === creationOpts.tabSize ? nls.localize('configuredTabSize', "Configured Tab Size") : undefined
227
		}));
I
isidor 已提交
228 229 230

		// auto focus the tabSize set for the current editor
		const autoFocusIndex = Math.min(model.getOptions().tabSize - 1, 7);
231

232
		setTimeout(() => {
C
Christof Marti 已提交
233
			quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File"), activeItem: picks[autoFocusIndex] }).then(pick => {
234
				if (pick) {
P
Peng Lyu 已提交
235 236 237 238 239 240
					if (model && !model.isDisposed()) {
						model.updateOptions({
							tabSize: parseInt(pick.label, 10),
							insertSpaces: this.insertSpaces
						});
					}
241
				}
242 243
			});
		}, 50/* quick open is sensitive to being opened so soon after another */);
244 245 246
	}
}

247 248
export class IndentUsingTabs extends ChangeIndentationSizeAction {

M
Matt Bierner 已提交
249
	public static readonly ID = 'editor.action.indentUsingTabs';
250

A
Alex Dima 已提交
251
	constructor() {
252 253 254 255
		super(false, {
			id: IndentUsingTabs.ID,
			label: nls.localize('indentUsingTabs', "Indent Using Tabs"),
			alias: 'Indent Using Tabs',
256
			precondition: undefined
257
		});
258 259 260 261 262
	}
}

export class IndentUsingSpaces extends ChangeIndentationSizeAction {

M
Matt Bierner 已提交
263
	public static readonly ID = 'editor.action.indentUsingSpaces';
264

A
Alex Dima 已提交
265
	constructor() {
266 267 268 269
		super(true, {
			id: IndentUsingSpaces.ID,
			label: nls.localize('indentUsingSpaces', "Indent Using Spaces"),
			alias: 'Indent Using Spaces',
270
			precondition: undefined
271
		});
272 273 274
	}
}

A
Alex Dima 已提交
275
export class DetectIndentation extends EditorAction {
276

M
Matt Bierner 已提交
277
	public static readonly ID = 'editor.action.detectIndentation';
278

A
Alex Dima 已提交
279
	constructor() {
280 281 282 283
		super({
			id: DetectIndentation.ID,
			label: nls.localize('detectIndentation', "Detect Indentation from Content"),
			alias: 'Detect Indentation from Content',
284
			precondition: undefined
285
		});
286 287
	}

288
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
A
Alex Dima 已提交
289 290 291
		const modelService = accessor.get(IModelService);

		let model = editor.getModel();
292 293 294 295
		if (!model) {
			return;
		}

296
		let creationOpts = modelService.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget);
297
		model.detectIndentation(creationOpts.insertSpaces, creationOpts.tabSize);
298 299
	}
}
300

R
rebornix 已提交
301 302 303 304 305 306
export class ReindentLinesAction extends EditorAction {
	constructor() {
		super({
			id: 'editor.action.reindentlines',
			label: nls.localize('editor.reindentlines', "Reindent Lines"),
			alias: 'Reindent Lines',
307
			precondition: EditorContextKeys.writable
R
rebornix 已提交
308 309 310
		});
	}

311
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
R
rebornix 已提交
312 313 314 315 316
		let model = editor.getModel();
		if (!model) {
			return;
		}
		let edits = getReindentEditOperations(model, 1, model.getLineCount());
P
Peng Lyu 已提交
317
		if (edits.length > 0) {
318
			editor.pushUndoStop();
R
rebornix 已提交
319
			editor.executeEdits(this.id, edits);
320
			editor.pushUndoStop();
R
rebornix 已提交
321
		}
R
rebornix 已提交
322 323 324
	}
}

K
Koji Murata 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
export class ReindentSelectedLinesAction extends EditorAction {
	constructor() {
		super({
			id: 'editor.action.reindentselectedlines',
			label: nls.localize('editor.reindentselectedlines', "Reindent Selected Lines"),
			alias: 'Reindent Selected Lines',
			precondition: EditorContextKeys.writable
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let model = editor.getModel();
		if (!model) {
			return;
		}
P
Peng Lyu 已提交
340

P
Peng Lyu 已提交
341 342 343 344 345
		let selections = editor.getSelections();
		if (selections === null) {
			return;
		}

K
Koji Murata 已提交
346 347
		let edits: IIdentifiedSingleEditOperation[] = [];

P
Peng Lyu 已提交
348
		for (let selection of selections) {
K
Koji Murata 已提交
349 350
			let startLineNumber = selection.startLineNumber;
			let endLineNumber = selection.endLineNumber;
P
Peng Lyu 已提交
351

K
Koji Murata 已提交
352 353 354
			if (startLineNumber !== endLineNumber && selection.endColumn === 1) {
				endLineNumber--;
			}
P
Peng Lyu 已提交
355

K
Koji Murata 已提交
356
			if (startLineNumber === 1) {
P
Peng Lyu 已提交
357 358 359
				if (startLineNumber === endLineNumber) {
					continue;
				}
K
Koji Murata 已提交
360 361 362
			} else {
				startLineNumber--;
			}
P
Peng Lyu 已提交
363

P
Peng Lyu 已提交
364
			let editOperations = getReindentEditOperations(model, startLineNumber, endLineNumber);
P
Peng Lyu 已提交
365
			edits.push(...editOperations);
K
Koji Murata 已提交
366 367
		}

P
Peng Lyu 已提交
368
		if (edits.length > 0) {
K
Koji Murata 已提交
369 370 371 372 373 374 375
			editor.pushUndoStop();
			editor.executeEdits(this.id, edits);
			editor.pushUndoStop();
		}
	}
}

R
rebornix 已提交
376 377
export class AutoIndentOnPasteCommand implements ICommand {

378
	private readonly _edits: { range: IRange; text: string; eol?: EndOfLineSequence; }[];
R
rebornix 已提交
379

380
	private readonly _initialSelection: Selection;
R
rebornix 已提交
381 382 383 384 385 386 387 388
	private _selectionId: string;

	constructor(edits: TextEdit[], initialSelection: Selection) {
		this._initialSelection = initialSelection;
		this._edits = [];

		for (let edit of edits) {
			if (edit.range && typeof edit.text === 'string') {
P
Peng Lyu 已提交
389
				this._edits.push(edit as { range: IRange; text: string; eol?: EndOfLineSequence; });
R
rebornix 已提交
390 391 392 393
			}
		}
	}

A
Alex Dima 已提交
394
	public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
R
rebornix 已提交
395 396 397 398
		for (let edit of this._edits) {
			builder.addEditOperation(Range.lift(edit.range), edit.text);
		}

A
Alex Dima 已提交
399
		let selectionIsSet = false;
R
rebornix 已提交
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
		if (Array.isArray(this._edits) && this._edits.length === 1 && this._initialSelection.isEmpty()) {
			if (this._edits[0].range.startColumn === this._initialSelection.endColumn &&
				this._edits[0].range.startLineNumber === this._initialSelection.endLineNumber) {
				selectionIsSet = true;
				this._selectionId = builder.trackSelection(this._initialSelection, true);
			} else if (this._edits[0].range.endColumn === this._initialSelection.startColumn &&
				this._edits[0].range.endLineNumber === this._initialSelection.startLineNumber) {
				selectionIsSet = true;
				this._selectionId = builder.trackSelection(this._initialSelection, false);
			}
		}

		if (!selectionIsSet) {
			this._selectionId = builder.trackSelection(this._initialSelection);
		}
	}

A
Alex Dima 已提交
417
	public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
R
rebornix 已提交
418 419 420 421 422
		return helper.getTrackedSelection(this._selectionId);
	}
}

export class AutoIndentOnPaste implements IEditorContribution {
423
	private static readonly ID = 'editor.contrib.autoIndentOnPaste';
R
rebornix 已提交
424

425
	private readonly editor: ICodeEditor;
R
rebornix 已提交
426 427 428
	private callOnDispose: IDisposable[];
	private callOnModel: IDisposable[];

429
	constructor(editor: ICodeEditor) {
R
rebornix 已提交
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
		this.editor = editor;
		this.callOnDispose = [];
		this.callOnModel = [];

		this.callOnDispose.push(editor.onDidChangeConfiguration(() => this.update()));
		this.callOnDispose.push(editor.onDidChangeModel(() => this.update()));
		this.callOnDispose.push(editor.onDidChangeModelLanguage(() => this.update()));
	}

	private update(): void {

		// clean up
		this.callOnModel = dispose(this.callOnModel);

		// we are disabled
R
rebornix 已提交
445
		if (!this.editor.getConfiguration().autoIndent || this.editor.getConfiguration().contribInfo.formatOnPaste) {
R
rebornix 已提交
446 447 448 449
			return;
		}

		// no model
M
Matt Bierner 已提交
450
		if (!this.editor.hasModel()) {
R
rebornix 已提交
451 452 453 454 455 456 457 458 459
			return;
		}

		this.callOnModel.push(this.editor.onDidPaste((range: Range) => {
			this.trigger(range);
		}));
	}

	private trigger(range: Range): void {
P
Peng Lyu 已提交
460 461
		let selections = this.editor.getSelections();
		if (selections === null || selections.length > 1) {
R
rebornix 已提交
462 463 464 465
			return;
		}

		const model = this.editor.getModel();
P
Peng Lyu 已提交
466 467 468 469
		if (!model) {
			return;
		}

470
		if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
471 472
			return;
		}
A
Alex Dima 已提交
473
		const { tabSize, indentSize, insertSpaces } = model.getOptions();
R
rebornix 已提交
474 475 476 477
		this.editor.pushUndoStop();
		let textEdits: TextEdit[] = [];

		let indentConverter = {
478
			shiftIndent: (indentation: string) => {
A
Alex Dima 已提交
479
				return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
R
rebornix 已提交
480
			},
481
			unshiftIndent: (indentation: string) => {
A
Alex Dima 已提交
482
				return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces);
R
rebornix 已提交
483 484
			}
		};
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499

		let startLineNumber = range.startLineNumber;

		while (startLineNumber <= range.endLineNumber) {
			if (this.shouldIgnoreLine(model, startLineNumber)) {
				startLineNumber++;
				continue;
			}
			break;
		}

		if (startLineNumber > range.endLineNumber) {
			return;
		}

500 501 502 503 504 505
		let firstLineText = model.getLineContent(startLineNumber);
		if (!/\S/.test(firstLineText.substring(0, range.startColumn - 1))) {
			let indentOfFirstLine = LanguageConfigurationRegistry.getGoodIndentForLine(model, model.getLanguageIdentifier().id, startLineNumber, indentConverter);

			if (indentOfFirstLine !== null) {
				let oldIndentation = strings.getLeadingWhitespace(firstLineText);
A
Alex Dima 已提交
506 507
				let newSpaceCnt = indentUtils.getSpaceCnt(indentOfFirstLine, tabSize);
				let oldSpaceCnt = indentUtils.getSpaceCnt(oldIndentation, tabSize);
508 509

				if (newSpaceCnt !== oldSpaceCnt) {
A
Alex Dima 已提交
510
					let newIndent = indentUtils.generateIndent(newSpaceCnt, tabSize, insertSpaces);
511 512 513 514 515
					textEdits.push({
						range: new Range(startLineNumber, 1, startLineNumber, oldIndentation.length + 1),
						text: newIndent
					});
					firstLineText = newIndent + firstLineText.substr(oldIndentation.length);
516 517 518 519 520 521 522 523 524 525
				} else {
					let indentMetadata = LanguageConfigurationRegistry.getIndentMetadata(model, startLineNumber);

					if (indentMetadata === 0 || indentMetadata === IndentConsts.UNINDENT_MASK) {
						// we paste content into a line where only contains whitespaces
						// after pasting, the indentation of the first line is already correct
						// the first line doesn't match any indentation rule
						// then no-op.
						return;
					}
526
				}
R
rebornix 已提交
527
			}
528
		}
R
rebornix 已提交
529

P
Peng Lyu 已提交
530 531 532 533 534 535 536 537 538 539 540
		const firstLineNumber = startLineNumber;

		// ignore empty or ignored lines
		while (startLineNumber < range.endLineNumber) {
			if (!/\S/.test(model.getLineContent(startLineNumber + 1))) {
				startLineNumber++;
				continue;
			}
			break;
		}

541 542 543 544 545 546 547 548 549 550 551
		if (startLineNumber !== range.endLineNumber) {
			let virtualModel = {
				getLineTokens: (lineNumber: number) => {
					return model.getLineTokens(lineNumber);
				},
				getLanguageIdentifier: () => {
					return model.getLanguageIdentifier();
				},
				getLanguageIdAtPosition: (lineNumber: number, column: number) => {
					return model.getLanguageIdAtPosition(lineNumber, column);
				},
552
				getLineContent: (lineNumber: number) => {
P
Peng Lyu 已提交
553
					if (lineNumber === firstLineNumber) {
554 555 556
						return firstLineText;
					} else {
						return model.getLineContent(lineNumber);
R
rebornix 已提交
557
					}
558 559 560
				}
			};
			let indentOfSecondLine = LanguageConfigurationRegistry.getGoodIndentForLine(virtualModel, model.getLanguageIdentifier().id, startLineNumber + 1, indentConverter);
R
rebornix 已提交
561
			if (indentOfSecondLine !== null) {
A
Alex Dima 已提交
562 563
				let newSpaceCntOfSecondLine = indentUtils.getSpaceCnt(indentOfSecondLine, tabSize);
				let oldSpaceCntOfSecondLine = indentUtils.getSpaceCnt(strings.getLeadingWhitespace(model.getLineContent(startLineNumber + 1)), tabSize);
R
rebornix 已提交
564 565 566 567 568 569

				if (newSpaceCntOfSecondLine !== oldSpaceCntOfSecondLine) {
					let spaceCntOffset = newSpaceCntOfSecondLine - oldSpaceCntOfSecondLine;
					for (let i = startLineNumber + 1; i <= range.endLineNumber; i++) {
						let lineContent = model.getLineContent(i);
						let originalIndent = strings.getLeadingWhitespace(lineContent);
A
Alex Dima 已提交
570
						let originalSpacesCnt = indentUtils.getSpaceCnt(originalIndent, tabSize);
R
rebornix 已提交
571
						let newSpacesCnt = originalSpacesCnt + spaceCntOffset;
A
Alex Dima 已提交
572
						let newIndent = indentUtils.generateIndent(newSpacesCnt, tabSize, insertSpaces);
R
rebornix 已提交
573 574 575 576 577 578 579

						if (newIndent !== originalIndent) {
							textEdits.push({
								range: new Range(i, 1, i, originalIndent.length + 1),
								text: newIndent
							});
						}
R
rebornix 已提交
580 581 582 583 584
					}
				}
			}
		}

P
Peng Lyu 已提交
585
		let cmd = new AutoIndentOnPasteCommand(textEdits, this.editor.getSelection()!);
R
rebornix 已提交
586 587 588 589
		this.editor.executeCommand('autoIndentOnPaste', cmd);
		this.editor.pushUndoStop();
	}

A
Alex Dima 已提交
590
	private shouldIgnoreLine(model: ITextModel, lineNumber: number): boolean {
591
		model.forceTokenization(lineNumber);
592 593
		let nonWhitespaceColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
		if (nonWhitespaceColumn === 0) {
594 595 596
			return true;
		}
		let tokens = model.getLineTokens(lineNumber);
597
		if (tokens.getCount() > 0) {
598
			let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhitespaceColumn);
A
Alex Dima 已提交
599
			if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === StandardTokenType.Comment) {
600 601 602 603 604 605 606
				return true;
			}
		}

		return false;
	}

R
rebornix 已提交
607 608 609 610 611 612 613 614 615 616
	public getId(): string {
		return AutoIndentOnPaste.ID;
	}

	public dispose(): void {
		this.callOnDispose = dispose(this.callOnDispose);
		this.callOnModel = dispose(this.callOnModel);
	}
}

A
Alex Dima 已提交
617
function getIndentationEditOperations(model: ITextModel, builder: IEditOperationBuilder, tabSize: number, tabsToSpaces: boolean): void {
618 619 620 621 622 623 624 625 626 627
	if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) {
		// Model is empty
		return;
	}

	let spaces = '';
	for (let i = 0; i < tabSize; i++) {
		spaces += ' ';
	}

628 629 630 631
	let spacesRegExp = new RegExp(spaces, 'gi');

	for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
		let lastIndentationColumn = model.getLineFirstNonWhitespaceColumn(lineNumber);
632
		if (lastIndentationColumn === 0) {
633
			lastIndentationColumn = model.getLineMaxColumn(lineNumber);
634 635
		}

636 637 638 639 640 641 642 643 644 645 646
		if (lastIndentationColumn === 1) {
			continue;
		}

		const originalIndentationRange = new Range(lineNumber, 1, lineNumber, lastIndentationColumn);
		const originalIndentation = model.getValueInRange(originalIndentationRange);
		const newIndentation = (
			tabsToSpaces
				? originalIndentation.replace(/\t/ig, spaces)
				: originalIndentation.replace(spacesRegExp, '\t')
		);
647

648
		builder.addEditOperation(originalIndentationRange, newIndentation);
649 650 651 652 653 654 655
	}
}

export class IndentationToSpacesCommand implements ICommand {

	private selectionId: string;

656
	constructor(private readonly selection: Selection, private tabSize: number) { }
657

A
Alex Dima 已提交
658
	public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
659 660 661 662
		this.selectionId = builder.trackSelection(this.selection);
		getIndentationEditOperations(model, builder, this.tabSize, true);
	}

A
Alex Dima 已提交
663
	public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
664 665 666 667 668 669 670 671
		return helper.getTrackedSelection(this.selectionId);
	}
}

export class IndentationToTabsCommand implements ICommand {

	private selectionId: string;

672
	constructor(private readonly selection: Selection, private tabSize: number) { }
673

A
Alex Dima 已提交
674
	public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
675 676 677 678
		this.selectionId = builder.trackSelection(this.selection);
		getIndentationEditOperations(model, builder, this.tabSize, false);
	}

A
Alex Dima 已提交
679
	public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
680 681 682
		return helper.getTrackedSelection(this.selectionId);
	}
}
683

684
registerEditorContribution(AutoIndentOnPaste);
685 686 687 688 689 690
registerEditorAction(IndentationToSpacesAction);
registerEditorAction(IndentationToTabsAction);
registerEditorAction(IndentUsingTabs);
registerEditorAction(IndentUsingSpaces);
registerEditorAction(DetectIndentation);
registerEditorAction(ReindentLinesAction);
K
Koji Murata 已提交
691
registerEditorAction(ReindentSelectedLinesAction);