Path: blob/main/src/vs/editor/contrib/comment/browser/blockCommentCommand.ts
3296 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 { CharCode } from '../../../../base/common/charCode.js';6import { EditOperation, ISingleEditOperation } from '../../../common/core/editOperation.js';7import { Position } from '../../../common/core/position.js';8import { Range } from '../../../common/core/range.js';9import { Selection } from '../../../common/core/selection.js';10import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from '../../../common/editorCommon.js';11import { ITextModel } from '../../../common/model.js';12import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';1314export class BlockCommentCommand implements ICommand {1516private readonly _selection: Selection;17private readonly _insertSpace: boolean;18private _usedEndToken: string | null;1920constructor(21selection: Selection,22insertSpace: boolean,23private readonly languageConfigurationService: ILanguageConfigurationService24) {25this._selection = selection;26this._insertSpace = insertSpace;27this._usedEndToken = null;28}2930public static _haystackHasNeedleAtOffset(haystack: string, needle: string, offset: number): boolean {31if (offset < 0) {32return false;33}34const needleLength = needle.length;35const haystackLength = haystack.length;36if (offset + needleLength > haystackLength) {37return false;38}3940for (let i = 0; i < needleLength; i++) {41const codeA = haystack.charCodeAt(offset + i);42const codeB = needle.charCodeAt(i);4344if (codeA === codeB) {45continue;46}47if (codeA >= CharCode.A && codeA <= CharCode.Z && codeA + 32 === codeB) {48// codeA is upper-case variant of codeB49continue;50}51if (codeB >= CharCode.A && codeB <= CharCode.Z && codeB + 32 === codeA) {52// codeB is upper-case variant of codeA53continue;54}5556return false;57}58return true;59}6061private _createOperationsForBlockComment(selection: Range, startToken: string, endToken: string, insertSpace: boolean, model: ITextModel, builder: IEditOperationBuilder): void {62const startLineNumber = selection.startLineNumber;63const startColumn = selection.startColumn;64const endLineNumber = selection.endLineNumber;65const endColumn = selection.endColumn;6667const startLineText = model.getLineContent(startLineNumber);68const endLineText = model.getLineContent(endLineNumber);6970let startTokenIndex = startLineText.lastIndexOf(startToken, startColumn - 1 + startToken.length);71let endTokenIndex = endLineText.indexOf(endToken, endColumn - 1 - endToken.length);7273if (startTokenIndex !== -1 && endTokenIndex !== -1) {7475if (startLineNumber === endLineNumber) {76const lineBetweenTokens = startLineText.substring(startTokenIndex + startToken.length, endTokenIndex);7778if (lineBetweenTokens.indexOf(endToken) >= 0) {79// force to add a block comment80startTokenIndex = -1;81endTokenIndex = -1;82}83} else {84const startLineAfterStartToken = startLineText.substring(startTokenIndex + startToken.length);85const endLineBeforeEndToken = endLineText.substring(0, endTokenIndex);8687if (startLineAfterStartToken.indexOf(endToken) >= 0 || endLineBeforeEndToken.indexOf(endToken) >= 0) {88// force to add a block comment89startTokenIndex = -1;90endTokenIndex = -1;91}92}93}9495let ops: ISingleEditOperation[];9697if (startTokenIndex !== -1 && endTokenIndex !== -1) {98// Consider spaces as part of the comment tokens99if (insertSpace && startTokenIndex + startToken.length < startLineText.length && startLineText.charCodeAt(startTokenIndex + startToken.length) === CharCode.Space) {100// Pretend the start token contains a trailing space101startToken = startToken + ' ';102}103104if (insertSpace && endTokenIndex > 0 && endLineText.charCodeAt(endTokenIndex - 1) === CharCode.Space) {105// Pretend the end token contains a leading space106endToken = ' ' + endToken;107endTokenIndex -= 1;108}109ops = BlockCommentCommand._createRemoveBlockCommentOperations(110new Range(startLineNumber, startTokenIndex + startToken.length + 1, endLineNumber, endTokenIndex + 1), startToken, endToken111);112} else {113ops = BlockCommentCommand._createAddBlockCommentOperations(selection, startToken, endToken, this._insertSpace);114this._usedEndToken = ops.length === 1 ? endToken : null;115}116117for (const op of ops) {118builder.addTrackedEditOperation(op.range, op.text);119}120}121122public static _createRemoveBlockCommentOperations(r: Range, startToken: string, endToken: string): ISingleEditOperation[] {123const res: ISingleEditOperation[] = [];124125if (!Range.isEmpty(r)) {126// Remove block comment start127res.push(EditOperation.delete(new Range(128r.startLineNumber, r.startColumn - startToken.length,129r.startLineNumber, r.startColumn130)));131132// Remove block comment end133res.push(EditOperation.delete(new Range(134r.endLineNumber, r.endColumn,135r.endLineNumber, r.endColumn + endToken.length136)));137} else {138// Remove both continuously139res.push(EditOperation.delete(new Range(140r.startLineNumber, r.startColumn - startToken.length,141r.endLineNumber, r.endColumn + endToken.length142)));143}144145return res;146}147148public static _createAddBlockCommentOperations(r: Range, startToken: string, endToken: string, insertSpace: boolean): ISingleEditOperation[] {149const res: ISingleEditOperation[] = [];150151if (!Range.isEmpty(r)) {152// Insert block comment start153res.push(EditOperation.insert(new Position(r.startLineNumber, r.startColumn), startToken + (insertSpace ? ' ' : '')));154155// Insert block comment end156res.push(EditOperation.insert(new Position(r.endLineNumber, r.endColumn), (insertSpace ? ' ' : '') + endToken));157} else {158// Insert both continuously159res.push(EditOperation.replace(new Range(160r.startLineNumber, r.startColumn,161r.endLineNumber, r.endColumn162), startToken + ' ' + endToken));163}164165return res;166}167168public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {169const startLineNumber = this._selection.startLineNumber;170const startColumn = this._selection.startColumn;171172model.tokenization.tokenizeIfCheap(startLineNumber);173const languageId = model.getLanguageIdAtPosition(startLineNumber, startColumn);174const config = this.languageConfigurationService.getLanguageConfiguration(languageId).comments;175if (!config || !config.blockCommentStartToken || !config.blockCommentEndToken) {176// Mode does not support block comments177return;178}179180this._createOperationsForBlockComment(this._selection, config.blockCommentStartToken, config.blockCommentEndToken, this._insertSpace, model, builder);181}182183public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {184const inverseEditOperations = helper.getInverseEditOperations();185if (inverseEditOperations.length === 2) {186const startTokenEditOperation = inverseEditOperations[0];187const endTokenEditOperation = inverseEditOperations[1];188189return new Selection(190startTokenEditOperation.range.endLineNumber,191startTokenEditOperation.range.endColumn,192endTokenEditOperation.range.startLineNumber,193endTokenEditOperation.range.startColumn194);195} else {196const srcRange = inverseEditOperations[0].range;197const deltaColumn = this._usedEndToken ? -this._usedEndToken.length - 1 : 0; // minus 1 space before endToken198return new Selection(199srcRange.endLineNumber,200srcRange.endColumn + deltaColumn,201srcRange.endLineNumber,202srcRange.endColumn + deltaColumn203);204}205}206}207208209