Path: blob/main/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as strings from '../../../base/common/strings.js';6import { EditOperation, ISingleEditOperation } from '../core/editOperation.js';7import { Position } from '../core/position.js';8import { Range } from '../core/range.js';9import { Selection } from '../core/selection.js';10import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from '../editorCommon.js';11import { StandardTokenType } from '../encodedTokenAttributes.js';12import { ITextModel } from '../model.js';1314export class TrimTrailingWhitespaceCommand implements ICommand {1516private readonly _selection: Selection;17private _selectionId: string | null;18private readonly _cursors: Position[];19private readonly _trimInRegexesAndStrings: boolean;2021constructor(selection: Selection, cursors: Position[], trimInRegexesAndStrings: boolean) {22this._selection = selection;23this._cursors = cursors;24this._selectionId = null;25this._trimInRegexesAndStrings = trimInRegexesAndStrings;26}2728public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {29const ops = trimTrailingWhitespace(model, this._cursors, this._trimInRegexesAndStrings);30for (let i = 0, len = ops.length; i < len; i++) {31const op = ops[i];3233builder.addEditOperation(op.range, op.text);34}3536this._selectionId = builder.trackSelection(this._selection);37}3839public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {40return helper.getTrackedSelection(this._selectionId!);41}42}4344/**45* Generate commands for trimming trailing whitespace on a model and ignore lines on which cursors are sitting.46*/47export function trimTrailingWhitespace(model: ITextModel, cursors: Position[], trimInRegexesAndStrings: boolean): ISingleEditOperation[] {48// Sort cursors ascending49cursors.sort((a, b) => {50if (a.lineNumber === b.lineNumber) {51return a.column - b.column;52}53return a.lineNumber - b.lineNumber;54});5556// Reduce multiple cursors on the same line and only keep the last one on the line57for (let i = cursors.length - 2; i >= 0; i--) {58if (cursors[i].lineNumber === cursors[i + 1].lineNumber) {59// Remove cursor at `i`60cursors.splice(i, 1);61}62}6364const r: ISingleEditOperation[] = [];65let rLen = 0;66let cursorIndex = 0;67const cursorLen = cursors.length;6869for (let lineNumber = 1, lineCount = model.getLineCount(); lineNumber <= lineCount; lineNumber++) {70const lineContent = model.getLineContent(lineNumber);71const maxLineColumn = lineContent.length + 1;72let minEditColumn = 0;7374if (cursorIndex < cursorLen && cursors[cursorIndex].lineNumber === lineNumber) {75minEditColumn = cursors[cursorIndex].column;76cursorIndex++;77if (minEditColumn === maxLineColumn) {78// The cursor is at the end of the line => no edits for sure on this line79continue;80}81}8283if (lineContent.length === 0) {84continue;85}8687const lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);8889let fromColumn = 0;90if (lastNonWhitespaceIndex === -1) {91// Entire line is whitespace92fromColumn = 1;93} else if (lastNonWhitespaceIndex !== lineContent.length - 1) {94// There is trailing whitespace95fromColumn = lastNonWhitespaceIndex + 2;96} else {97// There is no trailing whitespace98continue;99}100101if (!trimInRegexesAndStrings) {102if (!model.tokenization.hasAccurateTokensForLine(lineNumber)) {103// We don't want to force line tokenization, as that can be expensive, but we also don't want to trim104// trailing whitespace in lines that are not tokenized yet, as that can be wrong and trim whitespace from105// lines that the user requested we don't. So we bail out if the tokens are not accurate for this line.106continue;107}108109const lineTokens = model.tokenization.getLineTokens(lineNumber);110const fromColumnType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(fromColumn));111112if (fromColumnType === StandardTokenType.String || fromColumnType === StandardTokenType.RegEx) {113continue;114}115}116117fromColumn = Math.max(minEditColumn, fromColumn);118r[rLen++] = EditOperation.delete(new Range(119lineNumber, fromColumn,120lineNumber, maxLineColumn121));122}123124return r;125}126127128