f108c3da8c5a83e8e588c6da5a867af9ac4b942554fe61f7bd163bfd7743ae4b5a17787be569f26791a3fe2a9dca06d6a70bf55b1583e7048d9521420db37d 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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 { SingleCursorState } from '../cursorCommon.js';
  6. import { CursorColumns } from '../core/cursorColumns.js';
  7. import { Position } from '../core/position.js';
  8. import { Range } from '../core/range.js';
  9. import * as strings from '../../../base/common/strings.js';
  10. import { AtomicTabMoveOperations } from './cursorAtomicMoveOperations.js';
  11. export class CursorPosition {
  12. constructor(lineNumber, column, leftoverVisibleColumns) {
  13. this._cursorPositionBrand = undefined;
  14. this.lineNumber = lineNumber;
  15. this.column = column;
  16. this.leftoverVisibleColumns = leftoverVisibleColumns;
  17. }
  18. }
  19. export class MoveOperations {
  20. static leftPosition(model, position) {
  21. if (position.column > model.getLineMinColumn(position.lineNumber)) {
  22. return position.delta(undefined, -strings.prevCharLength(model.getLineContent(position.lineNumber), position.column - 1));
  23. }
  24. else if (position.lineNumber > 1) {
  25. const newLineNumber = position.lineNumber - 1;
  26. return new Position(newLineNumber, model.getLineMaxColumn(newLineNumber));
  27. }
  28. else {
  29. return position;
  30. }
  31. }
  32. static leftPositionAtomicSoftTabs(model, position, tabSize) {
  33. if (position.column <= model.getLineIndentColumn(position.lineNumber)) {
  34. const minColumn = model.getLineMinColumn(position.lineNumber);
  35. const lineContent = model.getLineContent(position.lineNumber);
  36. const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, position.column - 1, tabSize, 0 /* Direction.Left */);
  37. if (newPosition !== -1 && newPosition + 1 >= minColumn) {
  38. return new Position(position.lineNumber, newPosition + 1);
  39. }
  40. }
  41. return this.leftPosition(model, position);
  42. }
  43. static left(config, model, position) {
  44. const pos = config.stickyTabStops
  45. ? MoveOperations.leftPositionAtomicSoftTabs(model, position, config.tabSize)
  46. : MoveOperations.leftPosition(model, position);
  47. return new CursorPosition(pos.lineNumber, pos.column, 0);
  48. }
  49. /**
  50. * @param noOfColumns Must be either `1`
  51. * or `Math.round(viewModel.getLineContent(viewLineNumber).length / 2)` (for half lines).
  52. */
  53. static moveLeft(config, model, cursor, inSelectionMode, noOfColumns) {
  54. let lineNumber, column;
  55. if (cursor.hasSelection() && !inSelectionMode) {
  56. // If the user has a selection and does not want to extend it,
  57. // put the cursor at the beginning of the selection.
  58. lineNumber = cursor.selection.startLineNumber;
  59. column = cursor.selection.startColumn;
  60. }
  61. else {
  62. // This has no effect if noOfColumns === 1.
  63. // It is ok to do so in the half-line scenario.
  64. const pos = cursor.position.delta(undefined, -(noOfColumns - 1));
  65. // We clip the position before normalization, as normalization is not defined
  66. // for possibly negative columns.
  67. const normalizedPos = model.normalizePosition(MoveOperations.clipPositionColumn(pos, model), 0 /* PositionAffinity.Left */);
  68. const p = MoveOperations.left(config, model, normalizedPos);
  69. lineNumber = p.lineNumber;
  70. column = p.column;
  71. }
  72. return cursor.move(inSelectionMode, lineNumber, column, 0);
  73. }
  74. /**
  75. * Adjusts the column so that it is within min/max of the line.
  76. */
  77. static clipPositionColumn(position, model) {
  78. return new Position(position.lineNumber, MoveOperations.clipRange(position.column, model.getLineMinColumn(position.lineNumber), model.getLineMaxColumn(position.lineNumber)));
  79. }
  80. static clipRange(value, min, max) {
  81. if (value < min) {
  82. return min;
  83. }
  84. if (value > max) {
  85. return max;
  86. }
  87. return value;
  88. }
  89. static rightPosition(model, lineNumber, column) {
  90. if (column < model.getLineMaxColumn(lineNumber)) {
  91. column = column + strings.nextCharLength(model.getLineContent(lineNumber), column - 1);
  92. }
  93. else if (lineNumber < model.getLineCount()) {
  94. lineNumber = lineNumber + 1;
  95. column = model.getLineMinColumn(lineNumber);
  96. }
  97. return new Position(lineNumber, column);
  98. }
  99. static rightPositionAtomicSoftTabs(model, lineNumber, column, tabSize, indentSize) {
  100. if (column < model.getLineIndentColumn(lineNumber)) {
  101. const lineContent = model.getLineContent(lineNumber);
  102. const newPosition = AtomicTabMoveOperations.atomicPosition(lineContent, column - 1, tabSize, 1 /* Direction.Right */);
  103. if (newPosition !== -1) {
  104. return new Position(lineNumber, newPosition + 1);
  105. }
  106. }
  107. return this.rightPosition(model, lineNumber, column);
  108. }
  109. static right(config, model, position) {
  110. const pos = config.stickyTabStops
  111. ? MoveOperations.rightPositionAtomicSoftTabs(model, position.lineNumber, position.column, config.tabSize, config.indentSize)
  112. : MoveOperations.rightPosition(model, position.lineNumber, position.column);
  113. return new CursorPosition(pos.lineNumber, pos.column, 0);
  114. }
  115. static moveRight(config, model, cursor, inSelectionMode, noOfColumns) {
  116. let lineNumber, column;
  117. if (cursor.hasSelection() && !inSelectionMode) {
  118. // If we are in selection mode, move right without selection cancels selection and puts cursor at the end of the selection
  119. lineNumber = cursor.selection.endLineNumber;
  120. column = cursor.selection.endColumn;
  121. }
  122. else {
  123. const pos = cursor.position.delta(undefined, noOfColumns - 1);
  124. const normalizedPos = model.normalizePosition(MoveOperations.clipPositionColumn(pos, model), 1 /* PositionAffinity.Right */);
  125. const r = MoveOperations.right(config, model, normalizedPos);
  126. lineNumber = r.lineNumber;
  127. column = r.column;
  128. }
  129. return cursor.move(inSelectionMode, lineNumber, column, 0);
  130. }
  131. static vertical(config, model, lineNumber, column, leftoverVisibleColumns, newLineNumber, allowMoveOnEdgeLine, normalizationAffinity) {
  132. const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
  133. const lineCount = model.getLineCount();
  134. const wasOnFirstPosition = (lineNumber === 1 && column === 1);
  135. const wasOnLastPosition = (lineNumber === lineCount && column === model.getLineMaxColumn(lineNumber));
  136. const wasAtEdgePosition = (newLineNumber < lineNumber ? wasOnFirstPosition : wasOnLastPosition);
  137. lineNumber = newLineNumber;
  138. if (lineNumber < 1) {
  139. lineNumber = 1;
  140. if (allowMoveOnEdgeLine) {
  141. column = model.getLineMinColumn(lineNumber);
  142. }
  143. else {
  144. column = Math.min(model.getLineMaxColumn(lineNumber), column);
  145. }
  146. }
  147. else if (lineNumber > lineCount) {
  148. lineNumber = lineCount;
  149. if (allowMoveOnEdgeLine) {
  150. column = model.getLineMaxColumn(lineNumber);
  151. }
  152. else {
  153. column = Math.min(model.getLineMaxColumn(lineNumber), column);
  154. }
  155. }
  156. else {
  157. column = config.columnFromVisibleColumn(model, lineNumber, currentVisibleColumn);
  158. }
  159. if (wasAtEdgePosition) {
  160. leftoverVisibleColumns = 0;
  161. }
  162. else {
  163. leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
  164. }
  165. if (normalizationAffinity !== undefined) {
  166. const position = new Position(lineNumber, column);
  167. const newPosition = model.normalizePosition(position, normalizationAffinity);
  168. leftoverVisibleColumns = leftoverVisibleColumns + (column - newPosition.column);
  169. lineNumber = newPosition.lineNumber;
  170. column = newPosition.column;
  171. }
  172. return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
  173. }
  174. static down(config, model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnLastLine) {
  175. return this.vertical(config, model, lineNumber, column, leftoverVisibleColumns, lineNumber + count, allowMoveOnLastLine, 4 /* PositionAffinity.RightOfInjectedText */);
  176. }
  177. static moveDown(config, model, cursor, inSelectionMode, linesCount) {
  178. let lineNumber, column;
  179. if (cursor.hasSelection() && !inSelectionMode) {
  180. // If we are in selection mode, move down acts relative to the end of selection
  181. lineNumber = cursor.selection.endLineNumber;
  182. column = cursor.selection.endColumn;
  183. }
  184. else {
  185. lineNumber = cursor.position.lineNumber;
  186. column = cursor.position.column;
  187. }
  188. const r = MoveOperations.down(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true);
  189. return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
  190. }
  191. static translateDown(config, model, cursor) {
  192. const selection = cursor.selection;
  193. const selectionStart = MoveOperations.down(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
  194. const position = MoveOperations.down(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
  195. return new SingleCursorState(new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column), selectionStart.leftoverVisibleColumns, new Position(position.lineNumber, position.column), position.leftoverVisibleColumns);
  196. }
  197. static up(config, model, lineNumber, column, leftoverVisibleColumns, count, allowMoveOnFirstLine) {
  198. return this.vertical(config, model, lineNumber, column, leftoverVisibleColumns, lineNumber - count, allowMoveOnFirstLine, 3 /* PositionAffinity.LeftOfInjectedText */);
  199. }
  200. static moveUp(config, model, cursor, inSelectionMode, linesCount) {
  201. let lineNumber, column;
  202. if (cursor.hasSelection() && !inSelectionMode) {
  203. // If we are in selection mode, move up acts relative to the beginning of selection
  204. lineNumber = cursor.selection.startLineNumber;
  205. column = cursor.selection.startColumn;
  206. }
  207. else {
  208. lineNumber = cursor.position.lineNumber;
  209. column = cursor.position.column;
  210. }
  211. const r = MoveOperations.up(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true);
  212. return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
  213. }
  214. static translateUp(config, model, cursor) {
  215. const selection = cursor.selection;
  216. const selectionStart = MoveOperations.up(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
  217. const position = MoveOperations.up(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
  218. return new SingleCursorState(new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column), selectionStart.leftoverVisibleColumns, new Position(position.lineNumber, position.column), position.leftoverVisibleColumns);
  219. }
  220. static _isBlankLine(model, lineNumber) {
  221. if (model.getLineFirstNonWhitespaceColumn(lineNumber) === 0) {
  222. // empty or contains only whitespace
  223. return true;
  224. }
  225. return false;
  226. }
  227. static moveToPrevBlankLine(config, model, cursor, inSelectionMode) {
  228. let lineNumber = cursor.position.lineNumber;
  229. // If our current line is blank, move to the previous non-blank line
  230. while (lineNumber > 1 && this._isBlankLine(model, lineNumber)) {
  231. lineNumber--;
  232. }
  233. // Find the previous blank line
  234. while (lineNumber > 1 && !this._isBlankLine(model, lineNumber)) {
  235. lineNumber--;
  236. }
  237. return cursor.move(inSelectionMode, lineNumber, model.getLineMinColumn(lineNumber), 0);
  238. }
  239. static moveToNextBlankLine(config, model, cursor, inSelectionMode) {
  240. const lineCount = model.getLineCount();
  241. let lineNumber = cursor.position.lineNumber;
  242. // If our current line is blank, move to the next non-blank line
  243. while (lineNumber < lineCount && this._isBlankLine(model, lineNumber)) {
  244. lineNumber++;
  245. }
  246. // Find the next blank line
  247. while (lineNumber < lineCount && !this._isBlankLine(model, lineNumber)) {
  248. lineNumber++;
  249. }
  250. return cursor.move(inSelectionMode, lineNumber, model.getLineMinColumn(lineNumber), 0);
  251. }
  252. static moveToBeginningOfLine(config, model, cursor, inSelectionMode) {
  253. const lineNumber = cursor.position.lineNumber;
  254. const minColumn = model.getLineMinColumn(lineNumber);
  255. const firstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(lineNumber) || minColumn;
  256. let column;
  257. const relevantColumnNumber = cursor.position.column;
  258. if (relevantColumnNumber === firstNonBlankColumn) {
  259. column = minColumn;
  260. }
  261. else {
  262. column = firstNonBlankColumn;
  263. }
  264. return cursor.move(inSelectionMode, lineNumber, column, 0);
  265. }
  266. static moveToEndOfLine(config, model, cursor, inSelectionMode, sticky) {
  267. const lineNumber = cursor.position.lineNumber;
  268. const maxColumn = model.getLineMaxColumn(lineNumber);
  269. return cursor.move(inSelectionMode, lineNumber, maxColumn, sticky ? 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */ - maxColumn : 0);
  270. }
  271. static moveToBeginningOfBuffer(config, model, cursor, inSelectionMode) {
  272. return cursor.move(inSelectionMode, 1, 1, 0);
  273. }
  274. static moveToEndOfBuffer(config, model, cursor, inSelectionMode) {
  275. const lastLineNumber = model.getLineCount();
  276. const lastColumn = model.getLineMaxColumn(lastLineNumber);
  277. return cursor.move(inSelectionMode, lastLineNumber, lastColumn, 0);
  278. }
  279. }