091df604bb93b5b19f7e85ed48c10173918b51b922aaa2332be8088ce1b1825ba87692461d27ac4e1edd6152ec940400a798d26606667df4381ecd5562e288 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 { compareBy, findLastMaxBy, findMinBy } from '../../../base/common/arrays.js';
  6. import { CursorState } from '../cursorCommon.js';
  7. import { Cursor } from './oneCursor.js';
  8. import { Position } from '../core/position.js';
  9. import { Range } from '../core/range.js';
  10. import { Selection } from '../core/selection.js';
  11. export class CursorCollection {
  12. constructor(context) {
  13. this.context = context;
  14. this.cursors = [new Cursor(context)];
  15. this.lastAddedCursorIndex = 0;
  16. }
  17. dispose() {
  18. for (const cursor of this.cursors) {
  19. cursor.dispose(this.context);
  20. }
  21. }
  22. startTrackingSelections() {
  23. for (const cursor of this.cursors) {
  24. cursor.startTrackingSelection(this.context);
  25. }
  26. }
  27. stopTrackingSelections() {
  28. for (const cursor of this.cursors) {
  29. cursor.stopTrackingSelection(this.context);
  30. }
  31. }
  32. updateContext(context) {
  33. this.context = context;
  34. }
  35. ensureValidState() {
  36. for (const cursor of this.cursors) {
  37. cursor.ensureValidState(this.context);
  38. }
  39. }
  40. readSelectionFromMarkers() {
  41. return this.cursors.map(c => c.readSelectionFromMarkers(this.context));
  42. }
  43. getAll() {
  44. return this.cursors.map(c => c.asCursorState());
  45. }
  46. getViewPositions() {
  47. return this.cursors.map(c => c.viewState.position);
  48. }
  49. getTopMostViewPosition() {
  50. return findMinBy(this.cursors, compareBy(c => c.viewState.position, Position.compare)).viewState.position;
  51. }
  52. getBottomMostViewPosition() {
  53. return findLastMaxBy(this.cursors, compareBy(c => c.viewState.position, Position.compare)).viewState.position;
  54. }
  55. getSelections() {
  56. return this.cursors.map(c => c.modelState.selection);
  57. }
  58. getViewSelections() {
  59. return this.cursors.map(c => c.viewState.selection);
  60. }
  61. setSelections(selections) {
  62. this.setStates(CursorState.fromModelSelections(selections));
  63. }
  64. getPrimaryCursor() {
  65. return this.cursors[0].asCursorState();
  66. }
  67. setStates(states) {
  68. if (states === null) {
  69. return;
  70. }
  71. this.cursors[0].setState(this.context, states[0].modelState, states[0].viewState);
  72. this._setSecondaryStates(states.slice(1));
  73. }
  74. /**
  75. * Creates or disposes secondary cursors as necessary to match the number of `secondarySelections`.
  76. */
  77. _setSecondaryStates(secondaryStates) {
  78. const secondaryCursorsLength = this.cursors.length - 1;
  79. const secondaryStatesLength = secondaryStates.length;
  80. if (secondaryCursorsLength < secondaryStatesLength) {
  81. const createCnt = secondaryStatesLength - secondaryCursorsLength;
  82. for (let i = 0; i < createCnt; i++) {
  83. this._addSecondaryCursor();
  84. }
  85. }
  86. else if (secondaryCursorsLength > secondaryStatesLength) {
  87. const removeCnt = secondaryCursorsLength - secondaryStatesLength;
  88. for (let i = 0; i < removeCnt; i++) {
  89. this._removeSecondaryCursor(this.cursors.length - 2);
  90. }
  91. }
  92. for (let i = 0; i < secondaryStatesLength; i++) {
  93. this.cursors[i + 1].setState(this.context, secondaryStates[i].modelState, secondaryStates[i].viewState);
  94. }
  95. }
  96. killSecondaryCursors() {
  97. this._setSecondaryStates([]);
  98. }
  99. _addSecondaryCursor() {
  100. this.cursors.push(new Cursor(this.context));
  101. this.lastAddedCursorIndex = this.cursors.length - 1;
  102. }
  103. getLastAddedCursorIndex() {
  104. if (this.cursors.length === 1 || this.lastAddedCursorIndex === 0) {
  105. return 0;
  106. }
  107. return this.lastAddedCursorIndex;
  108. }
  109. _removeSecondaryCursor(removeIndex) {
  110. if (this.lastAddedCursorIndex >= removeIndex + 1) {
  111. this.lastAddedCursorIndex--;
  112. }
  113. this.cursors[removeIndex + 1].dispose(this.context);
  114. this.cursors.splice(removeIndex + 1, 1);
  115. }
  116. normalize() {
  117. if (this.cursors.length === 1) {
  118. return;
  119. }
  120. const cursors = this.cursors.slice(0);
  121. const sortedCursors = [];
  122. for (let i = 0, len = cursors.length; i < len; i++) {
  123. sortedCursors.push({
  124. index: i,
  125. selection: cursors[i].modelState.selection,
  126. });
  127. }
  128. sortedCursors.sort(compareBy(s => s.selection, Range.compareRangesUsingStarts));
  129. for (let sortedCursorIndex = 0; sortedCursorIndex < sortedCursors.length - 1; sortedCursorIndex++) {
  130. const current = sortedCursors[sortedCursorIndex];
  131. const next = sortedCursors[sortedCursorIndex + 1];
  132. const currentSelection = current.selection;
  133. const nextSelection = next.selection;
  134. if (!this.context.cursorConfig.multiCursorMergeOverlapping) {
  135. continue;
  136. }
  137. let shouldMergeCursors;
  138. if (nextSelection.isEmpty() || currentSelection.isEmpty()) {
  139. // Merge touching cursors if one of them is collapsed
  140. shouldMergeCursors = nextSelection.getStartPosition().isBeforeOrEqual(currentSelection.getEndPosition());
  141. }
  142. else {
  143. // Merge only overlapping cursors (i.e. allow touching ranges)
  144. shouldMergeCursors = nextSelection.getStartPosition().isBefore(currentSelection.getEndPosition());
  145. }
  146. if (shouldMergeCursors) {
  147. const winnerSortedCursorIndex = current.index < next.index ? sortedCursorIndex : sortedCursorIndex + 1;
  148. const looserSortedCursorIndex = current.index < next.index ? sortedCursorIndex + 1 : sortedCursorIndex;
  149. const looserIndex = sortedCursors[looserSortedCursorIndex].index;
  150. const winnerIndex = sortedCursors[winnerSortedCursorIndex].index;
  151. const looserSelection = sortedCursors[looserSortedCursorIndex].selection;
  152. const winnerSelection = sortedCursors[winnerSortedCursorIndex].selection;
  153. if (!looserSelection.equalsSelection(winnerSelection)) {
  154. const resultingRange = looserSelection.plusRange(winnerSelection);
  155. const looserSelectionIsLTR = (looserSelection.selectionStartLineNumber === looserSelection.startLineNumber && looserSelection.selectionStartColumn === looserSelection.startColumn);
  156. const winnerSelectionIsLTR = (winnerSelection.selectionStartLineNumber === winnerSelection.startLineNumber && winnerSelection.selectionStartColumn === winnerSelection.startColumn);
  157. // Give more importance to the last added cursor (think Ctrl-dragging + hitting another cursor)
  158. let resultingSelectionIsLTR;
  159. if (looserIndex === this.lastAddedCursorIndex) {
  160. resultingSelectionIsLTR = looserSelectionIsLTR;
  161. this.lastAddedCursorIndex = winnerIndex;
  162. }
  163. else {
  164. // Winner takes it all
  165. resultingSelectionIsLTR = winnerSelectionIsLTR;
  166. }
  167. let resultingSelection;
  168. if (resultingSelectionIsLTR) {
  169. resultingSelection = new Selection(resultingRange.startLineNumber, resultingRange.startColumn, resultingRange.endLineNumber, resultingRange.endColumn);
  170. }
  171. else {
  172. resultingSelection = new Selection(resultingRange.endLineNumber, resultingRange.endColumn, resultingRange.startLineNumber, resultingRange.startColumn);
  173. }
  174. sortedCursors[winnerSortedCursorIndex].selection = resultingSelection;
  175. const resultingState = CursorState.fromModelSelection(resultingSelection);
  176. cursors[winnerIndex].setState(this.context, resultingState.modelState, resultingState.viewState);
  177. }
  178. for (const sortedCursor of sortedCursors) {
  179. if (sortedCursor.index > looserIndex) {
  180. sortedCursor.index--;
  181. }
  182. }
  183. cursors.splice(looserIndex, 1);
  184. sortedCursors.splice(looserSortedCursorIndex, 1);
  185. this._removeSecondaryCursor(looserIndex - 1);
  186. sortedCursorIndex--;
  187. }
  188. }
  189. }
  190. }