blockCommentCommand.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. import { EditOperation } from '../../common/core/editOperation.js';
  6. import { Position } from '../../common/core/position.js';
  7. import { Range } from '../../common/core/range.js';
  8. import { Selection } from '../../common/core/selection.js';
  9. import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
  10. export class BlockCommentCommand {
  11. constructor(selection, insertSpace) {
  12. this._selection = selection;
  13. this._insertSpace = insertSpace;
  14. this._usedEndToken = null;
  15. }
  16. static _haystackHasNeedleAtOffset(haystack, needle, offset) {
  17. if (offset < 0) {
  18. return false;
  19. }
  20. const needleLength = needle.length;
  21. const haystackLength = haystack.length;
  22. if (offset + needleLength > haystackLength) {
  23. return false;
  24. }
  25. for (let i = 0; i < needleLength; i++) {
  26. const codeA = haystack.charCodeAt(offset + i);
  27. const codeB = needle.charCodeAt(i);
  28. if (codeA === codeB) {
  29. continue;
  30. }
  31. if (codeA >= 65 /* A */ && codeA <= 90 /* Z */ && codeA + 32 === codeB) {
  32. // codeA is upper-case variant of codeB
  33. continue;
  34. }
  35. if (codeB >= 65 /* A */ && codeB <= 90 /* Z */ && codeB + 32 === codeA) {
  36. // codeB is upper-case variant of codeA
  37. continue;
  38. }
  39. return false;
  40. }
  41. return true;
  42. }
  43. _createOperationsForBlockComment(selection, startToken, endToken, insertSpace, model, builder) {
  44. const startLineNumber = selection.startLineNumber;
  45. const startColumn = selection.startColumn;
  46. const endLineNumber = selection.endLineNumber;
  47. const endColumn = selection.endColumn;
  48. const startLineText = model.getLineContent(startLineNumber);
  49. const endLineText = model.getLineContent(endLineNumber);
  50. let startTokenIndex = startLineText.lastIndexOf(startToken, startColumn - 1 + startToken.length);
  51. let endTokenIndex = endLineText.indexOf(endToken, endColumn - 1 - endToken.length);
  52. if (startTokenIndex !== -1 && endTokenIndex !== -1) {
  53. if (startLineNumber === endLineNumber) {
  54. const lineBetweenTokens = startLineText.substring(startTokenIndex + startToken.length, endTokenIndex);
  55. if (lineBetweenTokens.indexOf(endToken) >= 0) {
  56. // force to add a block comment
  57. startTokenIndex = -1;
  58. endTokenIndex = -1;
  59. }
  60. }
  61. else {
  62. const startLineAfterStartToken = startLineText.substring(startTokenIndex + startToken.length);
  63. const endLineBeforeEndToken = endLineText.substring(0, endTokenIndex);
  64. if (startLineAfterStartToken.indexOf(endToken) >= 0 || endLineBeforeEndToken.indexOf(endToken) >= 0) {
  65. // force to add a block comment
  66. startTokenIndex = -1;
  67. endTokenIndex = -1;
  68. }
  69. }
  70. }
  71. let ops;
  72. if (startTokenIndex !== -1 && endTokenIndex !== -1) {
  73. // Consider spaces as part of the comment tokens
  74. if (insertSpace && startTokenIndex + startToken.length < startLineText.length && startLineText.charCodeAt(startTokenIndex + startToken.length) === 32 /* Space */) {
  75. // Pretend the start token contains a trailing space
  76. startToken = startToken + ' ';
  77. }
  78. if (insertSpace && endTokenIndex > 0 && endLineText.charCodeAt(endTokenIndex - 1) === 32 /* Space */) {
  79. // Pretend the end token contains a leading space
  80. endToken = ' ' + endToken;
  81. endTokenIndex -= 1;
  82. }
  83. ops = BlockCommentCommand._createRemoveBlockCommentOperations(new Range(startLineNumber, startTokenIndex + startToken.length + 1, endLineNumber, endTokenIndex + 1), startToken, endToken);
  84. }
  85. else {
  86. ops = BlockCommentCommand._createAddBlockCommentOperations(selection, startToken, endToken, this._insertSpace);
  87. this._usedEndToken = ops.length === 1 ? endToken : null;
  88. }
  89. for (const op of ops) {
  90. builder.addTrackedEditOperation(op.range, op.text);
  91. }
  92. }
  93. static _createRemoveBlockCommentOperations(r, startToken, endToken) {
  94. let res = [];
  95. if (!Range.isEmpty(r)) {
  96. // Remove block comment start
  97. res.push(EditOperation.delete(new Range(r.startLineNumber, r.startColumn - startToken.length, r.startLineNumber, r.startColumn)));
  98. // Remove block comment end
  99. res.push(EditOperation.delete(new Range(r.endLineNumber, r.endColumn, r.endLineNumber, r.endColumn + endToken.length)));
  100. }
  101. else {
  102. // Remove both continuously
  103. res.push(EditOperation.delete(new Range(r.startLineNumber, r.startColumn - startToken.length, r.endLineNumber, r.endColumn + endToken.length)));
  104. }
  105. return res;
  106. }
  107. static _createAddBlockCommentOperations(r, startToken, endToken, insertSpace) {
  108. let res = [];
  109. if (!Range.isEmpty(r)) {
  110. // Insert block comment start
  111. res.push(EditOperation.insert(new Position(r.startLineNumber, r.startColumn), startToken + (insertSpace ? ' ' : '')));
  112. // Insert block comment end
  113. res.push(EditOperation.insert(new Position(r.endLineNumber, r.endColumn), (insertSpace ? ' ' : '') + endToken));
  114. }
  115. else {
  116. // Insert both continuously
  117. res.push(EditOperation.replace(new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn), startToken + ' ' + endToken));
  118. }
  119. return res;
  120. }
  121. getEditOperations(model, builder) {
  122. const startLineNumber = this._selection.startLineNumber;
  123. const startColumn = this._selection.startColumn;
  124. model.tokenizeIfCheap(startLineNumber);
  125. const languageId = model.getLanguageIdAtPosition(startLineNumber, startColumn);
  126. const config = LanguageConfigurationRegistry.getComments(languageId);
  127. if (!config || !config.blockCommentStartToken || !config.blockCommentEndToken) {
  128. // Mode does not support block comments
  129. return;
  130. }
  131. this._createOperationsForBlockComment(this._selection, config.blockCommentStartToken, config.blockCommentEndToken, this._insertSpace, model, builder);
  132. }
  133. computeCursorState(model, helper) {
  134. const inverseEditOperations = helper.getInverseEditOperations();
  135. if (inverseEditOperations.length === 2) {
  136. const startTokenEditOperation = inverseEditOperations[0];
  137. const endTokenEditOperation = inverseEditOperations[1];
  138. return new Selection(startTokenEditOperation.range.endLineNumber, startTokenEditOperation.range.endColumn, endTokenEditOperation.range.startLineNumber, endTokenEditOperation.range.startColumn);
  139. }
  140. else {
  141. const srcRange = inverseEditOperations[0].range;
  142. const deltaColumn = this._usedEndToken ? -this._usedEndToken.length - 1 : 0; // minus 1 space before endToken
  143. return new Selection(srcRange.endLineNumber, srcRange.endColumn + deltaColumn, srcRange.endLineNumber, srcRange.endColumn + deltaColumn);
  144. }
  145. }
  146. }