e523a0cc17673f6a34129f878d4ddaa2c951d539158c8f9b53d64422c0f39bb660bfa6eaf6525e231026461144f624760c11de730a4344e7687b7cf41a71f6 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 { CursorColumns } from '../core/cursorColumns.js';
  6. export class AtomicTabMoveOperations {
  7. /**
  8. * Get the visible column at the position. If we get to a non-whitespace character first
  9. * or past the end of string then return -1.
  10. *
  11. * **Note** `position` and the return value are 0-based.
  12. */
  13. static whitespaceVisibleColumn(lineContent, position, tabSize) {
  14. const lineLength = lineContent.length;
  15. let visibleColumn = 0;
  16. let prevTabStopPosition = -1;
  17. let prevTabStopVisibleColumn = -1;
  18. for (let i = 0; i < lineLength; i++) {
  19. if (i === position) {
  20. return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
  21. }
  22. if (visibleColumn % tabSize === 0) {
  23. prevTabStopPosition = i;
  24. prevTabStopVisibleColumn = visibleColumn;
  25. }
  26. const chCode = lineContent.charCodeAt(i);
  27. switch (chCode) {
  28. case 32 /* CharCode.Space */:
  29. visibleColumn += 1;
  30. break;
  31. case 9 /* CharCode.Tab */:
  32. // Skip to the next multiple of tabSize.
  33. visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
  34. break;
  35. default:
  36. return [-1, -1, -1];
  37. }
  38. }
  39. if (position === lineLength) {
  40. return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn];
  41. }
  42. return [-1, -1, -1];
  43. }
  44. /**
  45. * Return the position that should result from a move left, right or to the
  46. * nearest tab, if atomic tabs are enabled. Left and right are used for the
  47. * arrow key movements, nearest is used for mouse selection. It returns
  48. * -1 if atomic tabs are not relevant and you should fall back to normal
  49. * behaviour.
  50. *
  51. * **Note**: `position` and the return value are 0-based.
  52. */
  53. static atomicPosition(lineContent, position, tabSize, direction) {
  54. const lineLength = lineContent.length;
  55. // Get the 0-based visible column corresponding to the position, or return
  56. // -1 if it is not in the initial whitespace.
  57. const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize);
  58. if (visibleColumn === -1) {
  59. return -1;
  60. }
  61. // Is the output left or right of the current position. The case for nearest
  62. // where it is the same as the current position is handled in the switch.
  63. let left;
  64. switch (direction) {
  65. case 0 /* Direction.Left */:
  66. left = true;
  67. break;
  68. case 1 /* Direction.Right */:
  69. left = false;
  70. break;
  71. case 2 /* Direction.Nearest */:
  72. // The code below assumes the output position is either left or right
  73. // of the input position. If it is the same, return immediately.
  74. if (visibleColumn % tabSize === 0) {
  75. return position;
  76. }
  77. // Go to the nearest indentation.
  78. left = visibleColumn % tabSize <= (tabSize / 2);
  79. break;
  80. }
  81. // If going left, we can just use the info about the last tab stop position and
  82. // last tab stop visible column that we computed in the first walk over the whitespace.
  83. if (left) {
  84. if (prevTabStopPosition === -1) {
  85. return -1;
  86. }
  87. // If the direction is left, we need to keep scanning right to ensure
  88. // that targetVisibleColumn + tabSize is before non-whitespace.
  89. // This is so that when we press left at the end of a partial
  90. // indentation it only goes one character. For example ' foo' with
  91. // tabSize 4, should jump from position 6 to position 5, not 4.
  92. let currentVisibleColumn = prevTabStopVisibleColumn;
  93. for (let i = prevTabStopPosition; i < lineLength; ++i) {
  94. if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
  95. // It is a full indentation.
  96. return prevTabStopPosition;
  97. }
  98. const chCode = lineContent.charCodeAt(i);
  99. switch (chCode) {
  100. case 32 /* CharCode.Space */:
  101. currentVisibleColumn += 1;
  102. break;
  103. case 9 /* CharCode.Tab */:
  104. currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
  105. break;
  106. default:
  107. return -1;
  108. }
  109. }
  110. if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) {
  111. return prevTabStopPosition;
  112. }
  113. // It must have been a partial indentation.
  114. return -1;
  115. }
  116. // We are going right.
  117. const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
  118. // We can just continue from where whitespaceVisibleColumn got to.
  119. let currentVisibleColumn = visibleColumn;
  120. for (let i = position; i < lineLength; i++) {
  121. if (currentVisibleColumn === targetVisibleColumn) {
  122. return i;
  123. }
  124. const chCode = lineContent.charCodeAt(i);
  125. switch (chCode) {
  126. case 32 /* CharCode.Space */:
  127. currentVisibleColumn += 1;
  128. break;
  129. case 9 /* CharCode.Tab */:
  130. currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize);
  131. break;
  132. default:
  133. return -1;
  134. }
  135. }
  136. // This condition handles when the target column is at the end of the line.
  137. if (currentVisibleColumn === targetVisibleColumn) {
  138. return lineLength;
  139. }
  140. return -1;
  141. }
  142. }