c84aa0ce3c169db23e6c3ced4082e753227a9d0763918291e3bfe20dfa0111e4da0da9e02ed2f80432906a50797e34e86271200ff23d5236af1e00ff47ac2c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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 * as strings from '../../../base/common/strings.js';
  6. /**
  7. * A column in a position is the gap between two adjacent characters. The methods here
  8. * work with a concept called "visible column". A visible column is a very rough approximation
  9. * of the horizontal screen position of a column. For example, using a tab size of 4:
  10. * ```txt
  11. * |<TAB>|<TAB>|T|ext
  12. * | | | \---- column = 4, visible column = 9
  13. * | | \------ column = 3, visible column = 8
  14. * | \------------ column = 2, visible column = 4
  15. * \------------------ column = 1, visible column = 0
  16. * ```
  17. *
  18. * **NOTE**: Visual columns do not work well for RTL text or variable-width fonts or characters.
  19. *
  20. * **NOTE**: These methods work and make sense both on the model and on the view model.
  21. */
  22. export class CursorColumns {
  23. static _nextVisibleColumn(codePoint, visibleColumn, tabSize) {
  24. if (codePoint === 9 /* CharCode.Tab */) {
  25. return CursorColumns.nextRenderTabStop(visibleColumn, tabSize);
  26. }
  27. if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) {
  28. return visibleColumn + 2;
  29. }
  30. return visibleColumn + 1;
  31. }
  32. /**
  33. * Returns a visible column from a column.
  34. * @see {@link CursorColumns}
  35. */
  36. static visibleColumnFromColumn(lineContent, column, tabSize) {
  37. const textLen = Math.min(column - 1, lineContent.length);
  38. const text = lineContent.substring(0, textLen);
  39. const iterator = new strings.GraphemeIterator(text);
  40. let result = 0;
  41. while (!iterator.eol()) {
  42. const codePoint = strings.getNextCodePoint(text, textLen, iterator.offset);
  43. iterator.nextGraphemeLength();
  44. result = this._nextVisibleColumn(codePoint, result, tabSize);
  45. }
  46. return result;
  47. }
  48. /**
  49. * Returns a column from a visible column.
  50. * @see {@link CursorColumns}
  51. */
  52. static columnFromVisibleColumn(lineContent, visibleColumn, tabSize) {
  53. if (visibleColumn <= 0) {
  54. return 1;
  55. }
  56. const lineContentLength = lineContent.length;
  57. const iterator = new strings.GraphemeIterator(lineContent);
  58. let beforeVisibleColumn = 0;
  59. let beforeColumn = 1;
  60. while (!iterator.eol()) {
  61. const codePoint = strings.getNextCodePoint(lineContent, lineContentLength, iterator.offset);
  62. iterator.nextGraphemeLength();
  63. const afterVisibleColumn = this._nextVisibleColumn(codePoint, beforeVisibleColumn, tabSize);
  64. const afterColumn = iterator.offset + 1;
  65. if (afterVisibleColumn >= visibleColumn) {
  66. const beforeDelta = visibleColumn - beforeVisibleColumn;
  67. const afterDelta = afterVisibleColumn - visibleColumn;
  68. if (afterDelta < beforeDelta) {
  69. return afterColumn;
  70. }
  71. else {
  72. return beforeColumn;
  73. }
  74. }
  75. beforeVisibleColumn = afterVisibleColumn;
  76. beforeColumn = afterColumn;
  77. }
  78. // walked the entire string
  79. return lineContentLength + 1;
  80. }
  81. /**
  82. * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
  83. * @see {@link CursorColumns}
  84. */
  85. static nextRenderTabStop(visibleColumn, tabSize) {
  86. return visibleColumn + tabSize - visibleColumn % tabSize;
  87. }
  88. /**
  89. * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
  90. * @see {@link CursorColumns}
  91. */
  92. static nextIndentTabStop(visibleColumn, indentSize) {
  93. return visibleColumn + indentSize - visibleColumn % indentSize;
  94. }
  95. /**
  96. * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
  97. * @see {@link CursorColumns}
  98. */
  99. static prevRenderTabStop(column, tabSize) {
  100. return Math.max(0, column - 1 - (column - 1) % tabSize);
  101. }
  102. /**
  103. * ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
  104. * @see {@link CursorColumns}
  105. */
  106. static prevIndentTabStop(column, indentSize) {
  107. return Math.max(0, column - 1 - (column - 1) % indentSize);
  108. }
  109. }