Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as strings from '../../../base/common/strings.js';
7
import { EditOperation, ISingleEditOperation } from '../core/editOperation.js';
8
import { Position } from '../core/position.js';
9
import { Range } from '../core/range.js';
10
import { Selection } from '../core/selection.js';
11
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from '../editorCommon.js';
12
import { StandardTokenType } from '../encodedTokenAttributes.js';
13
import { ITextModel } from '../model.js';
14
15
export class TrimTrailingWhitespaceCommand implements ICommand {
16
17
private readonly _selection: Selection;
18
private _selectionId: string | null;
19
private readonly _cursors: Position[];
20
private readonly _trimInRegexesAndStrings: boolean;
21
22
constructor(selection: Selection, cursors: Position[], trimInRegexesAndStrings: boolean) {
23
this._selection = selection;
24
this._cursors = cursors;
25
this._selectionId = null;
26
this._trimInRegexesAndStrings = trimInRegexesAndStrings;
27
}
28
29
public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
30
const ops = trimTrailingWhitespace(model, this._cursors, this._trimInRegexesAndStrings);
31
for (let i = 0, len = ops.length; i < len; i++) {
32
const op = ops[i];
33
34
builder.addEditOperation(op.range, op.text);
35
}
36
37
this._selectionId = builder.trackSelection(this._selection);
38
}
39
40
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
41
return helper.getTrackedSelection(this._selectionId!);
42
}
43
}
44
45
/**
46
* Generate commands for trimming trailing whitespace on a model and ignore lines on which cursors are sitting.
47
*/
48
export function trimTrailingWhitespace(model: ITextModel, cursors: Position[], trimInRegexesAndStrings: boolean): ISingleEditOperation[] {
49
// Sort cursors ascending
50
cursors.sort((a, b) => {
51
if (a.lineNumber === b.lineNumber) {
52
return a.column - b.column;
53
}
54
return a.lineNumber - b.lineNumber;
55
});
56
57
// Reduce multiple cursors on the same line and only keep the last one on the line
58
for (let i = cursors.length - 2; i >= 0; i--) {
59
if (cursors[i].lineNumber === cursors[i + 1].lineNumber) {
60
// Remove cursor at `i`
61
cursors.splice(i, 1);
62
}
63
}
64
65
const r: ISingleEditOperation[] = [];
66
let rLen = 0;
67
let cursorIndex = 0;
68
const cursorLen = cursors.length;
69
70
for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {
71
const lineContent = model.getLineContent(lineNumber);
72
const maxLineColumn = lineContent.length + 1;
73
let minEditColumn = 0;
74
75
if (cursorIndex < cursorLen && cursors[cursorIndex].lineNumber === lineNumber) {
76
minEditColumn = cursors[cursorIndex].column;
77
cursorIndex++;
78
if (minEditColumn === maxLineColumn) {
79
// The cursor is at the end of the line => no edits for sure on this line
80
continue;
81
}
82
}
83
84
if (lineContent.length === 0) {
85
continue;
86
}
87
88
const lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
89
90
let fromColumn = 0;
91
if (lastNonWhitespaceIndex === -1) {
92
// Entire line is whitespace
93
fromColumn = 1;
94
} else if (lastNonWhitespaceIndex !== lineContent.length - 1) {
95
// There is trailing whitespace
96
fromColumn = lastNonWhitespaceIndex + 2;
97
} else {
98
// There is no trailing whitespace
99
continue;
100
}
101
102
if (!trimInRegexesAndStrings) {
103
if (!model.tokenization.hasAccurateTokensForLine(lineNumber)) {
104
// We don't want to force line tokenization, as that can be expensive, but we also don't want to trim
105
// trailing whitespace in lines that are not tokenized yet, as that can be wrong and trim whitespace from
106
// lines that the user requested we don't. So we bail out if the tokens are not accurate for this line.
107
continue;
108
}
109
110
const lineTokens = model.tokenization.getLineTokens(lineNumber);
111
const fromColumnType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(fromColumn));
112
113
if (fromColumnType === StandardTokenType.String || fromColumnType === StandardTokenType.RegEx) {
114
continue;
115
}
116
}
117
118
fromColumn = Math.max(minEditColumn, fromColumn);
119
r[rLen++] = EditOperation.delete(new Range(
120
lineNumber, fromColumn,
121
lineNumber, maxLineColumn
122
));
123
}
124
125
return r;
126
}
127
128