2b1fe42650a5011dd0e6a229ec602c6fc904dc34caacd0d149dcd0d68267c98baedb7870826a8b12ba4e60e053c7e8c70c8ecc645d5f1d5460fa4a6425e13b 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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 './selections.css';
  6. import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js';
  7. import { editorInactiveSelection, editorSelectionBackground, editorSelectionForeground } from '../../../../platform/theme/common/colorRegistry.js';
  8. import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
  9. class HorizontalRangeWithStyle {
  10. constructor(other) {
  11. this.left = other.left;
  12. this.width = other.width;
  13. this.startStyle = null;
  14. this.endStyle = null;
  15. }
  16. }
  17. class LineVisibleRangesWithStyle {
  18. constructor(lineNumber, ranges) {
  19. this.lineNumber = lineNumber;
  20. this.ranges = ranges;
  21. }
  22. }
  23. function toStyledRange(item) {
  24. return new HorizontalRangeWithStyle(item);
  25. }
  26. function toStyled(item) {
  27. return new LineVisibleRangesWithStyle(item.lineNumber, item.ranges.map(toStyledRange));
  28. }
  29. export class SelectionsOverlay extends DynamicViewOverlay {
  30. constructor(context) {
  31. super();
  32. this._previousFrameVisibleRangesWithStyle = [];
  33. this._context = context;
  34. const options = this._context.configuration.options;
  35. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  36. this._roundedSelection = options.get(92 /* EditorOption.roundedSelection */);
  37. this._typicalHalfwidthCharacterWidth = options.get(46 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
  38. this._selections = [];
  39. this._renderResult = null;
  40. this._context.addEventHandler(this);
  41. }
  42. dispose() {
  43. this._context.removeEventHandler(this);
  44. this._renderResult = null;
  45. super.dispose();
  46. }
  47. // --- begin event handlers
  48. onConfigurationChanged(e) {
  49. const options = this._context.configuration.options;
  50. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  51. this._roundedSelection = options.get(92 /* EditorOption.roundedSelection */);
  52. this._typicalHalfwidthCharacterWidth = options.get(46 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth;
  53. return true;
  54. }
  55. onCursorStateChanged(e) {
  56. this._selections = e.selections.slice(0);
  57. return true;
  58. }
  59. onDecorationsChanged(e) {
  60. // true for inline decorations that can end up relayouting text
  61. return true; //e.inlineDecorationsChanged;
  62. }
  63. onFlushed(e) {
  64. return true;
  65. }
  66. onLinesChanged(e) {
  67. return true;
  68. }
  69. onLinesDeleted(e) {
  70. return true;
  71. }
  72. onLinesInserted(e) {
  73. return true;
  74. }
  75. onScrollChanged(e) {
  76. return e.scrollTopChanged;
  77. }
  78. onZonesChanged(e) {
  79. return true;
  80. }
  81. // --- end event handlers
  82. _visibleRangesHaveGaps(linesVisibleRanges) {
  83. for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
  84. const lineVisibleRanges = linesVisibleRanges[i];
  85. if (lineVisibleRanges.ranges.length > 1) {
  86. // There are two ranges on the same line
  87. return true;
  88. }
  89. }
  90. return false;
  91. }
  92. _enrichVisibleRangesWithStyle(viewport, linesVisibleRanges, previousFrame) {
  93. const epsilon = this._typicalHalfwidthCharacterWidth / 4;
  94. let previousFrameTop = null;
  95. let previousFrameBottom = null;
  96. if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) {
  97. const topLineNumber = linesVisibleRanges[0].lineNumber;
  98. if (topLineNumber === viewport.startLineNumber) {
  99. for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) {
  100. if (previousFrame[i].lineNumber === topLineNumber) {
  101. previousFrameTop = previousFrame[i].ranges[0];
  102. }
  103. }
  104. }
  105. const bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber;
  106. if (bottomLineNumber === viewport.endLineNumber) {
  107. for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) {
  108. if (previousFrame[i].lineNumber === bottomLineNumber) {
  109. previousFrameBottom = previousFrame[i].ranges[0];
  110. }
  111. }
  112. }
  113. if (previousFrameTop && !previousFrameTop.startStyle) {
  114. previousFrameTop = null;
  115. }
  116. if (previousFrameBottom && !previousFrameBottom.startStyle) {
  117. previousFrameBottom = null;
  118. }
  119. }
  120. for (let i = 0, len = linesVisibleRanges.length; i < len; i++) {
  121. // We know for a fact that there is precisely one range on each line
  122. const curLineRange = linesVisibleRanges[i].ranges[0];
  123. const curLeft = curLineRange.left;
  124. const curRight = curLineRange.left + curLineRange.width;
  125. const startStyle = {
  126. top: 0 /* CornerStyle.EXTERN */,
  127. bottom: 0 /* CornerStyle.EXTERN */
  128. };
  129. const endStyle = {
  130. top: 0 /* CornerStyle.EXTERN */,
  131. bottom: 0 /* CornerStyle.EXTERN */
  132. };
  133. if (i > 0) {
  134. // Look above
  135. const prevLeft = linesVisibleRanges[i - 1].ranges[0].left;
  136. const prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width;
  137. if (abs(curLeft - prevLeft) < epsilon) {
  138. startStyle.top = 2 /* CornerStyle.FLAT */;
  139. }
  140. else if (curLeft > prevLeft) {
  141. startStyle.top = 1 /* CornerStyle.INTERN */;
  142. }
  143. if (abs(curRight - prevRight) < epsilon) {
  144. endStyle.top = 2 /* CornerStyle.FLAT */;
  145. }
  146. else if (prevLeft < curRight && curRight < prevRight) {
  147. endStyle.top = 1 /* CornerStyle.INTERN */;
  148. }
  149. }
  150. else if (previousFrameTop) {
  151. // Accept some hiccups near the viewport edges to save on repaints
  152. startStyle.top = previousFrameTop.startStyle.top;
  153. endStyle.top = previousFrameTop.endStyle.top;
  154. }
  155. if (i + 1 < len) {
  156. // Look below
  157. const nextLeft = linesVisibleRanges[i + 1].ranges[0].left;
  158. const nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width;
  159. if (abs(curLeft - nextLeft) < epsilon) {
  160. startStyle.bottom = 2 /* CornerStyle.FLAT */;
  161. }
  162. else if (nextLeft < curLeft && curLeft < nextRight) {
  163. startStyle.bottom = 1 /* CornerStyle.INTERN */;
  164. }
  165. if (abs(curRight - nextRight) < epsilon) {
  166. endStyle.bottom = 2 /* CornerStyle.FLAT */;
  167. }
  168. else if (curRight < nextRight) {
  169. endStyle.bottom = 1 /* CornerStyle.INTERN */;
  170. }
  171. }
  172. else if (previousFrameBottom) {
  173. // Accept some hiccups near the viewport edges to save on repaints
  174. startStyle.bottom = previousFrameBottom.startStyle.bottom;
  175. endStyle.bottom = previousFrameBottom.endStyle.bottom;
  176. }
  177. curLineRange.startStyle = startStyle;
  178. curLineRange.endStyle = endStyle;
  179. }
  180. }
  181. _getVisibleRangesWithStyle(selection, ctx, previousFrame) {
  182. const _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || [];
  183. const linesVisibleRanges = _linesVisibleRanges.map(toStyled);
  184. const visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges);
  185. if (!visibleRangesHaveGaps && this._roundedSelection) {
  186. this._enrichVisibleRangesWithStyle(ctx.visibleRange, linesVisibleRanges, previousFrame);
  187. }
  188. // The visible ranges are sorted TOP-BOTTOM and LEFT-RIGHT
  189. return linesVisibleRanges;
  190. }
  191. _createSelectionPiece(top, height, className, left, width) {
  192. return ('<div class="cslr '
  193. + className
  194. + '" style="top:'
  195. + top.toString()
  196. + 'px;left:'
  197. + left.toString()
  198. + 'px;width:'
  199. + width.toString()
  200. + 'px;height:'
  201. + height
  202. + 'px;"></div>');
  203. }
  204. _actualRenderOneSelection(output2, visibleStartLineNumber, hasMultipleSelections, visibleRanges) {
  205. if (visibleRanges.length === 0) {
  206. return;
  207. }
  208. const visibleRangesHaveStyle = !!visibleRanges[0].ranges[0].startStyle;
  209. const fullLineHeight = (this._lineHeight).toString();
  210. const reducedLineHeight = (this._lineHeight - 1).toString();
  211. const firstLineNumber = visibleRanges[0].lineNumber;
  212. const lastLineNumber = visibleRanges[visibleRanges.length - 1].lineNumber;
  213. for (let i = 0, len = visibleRanges.length; i < len; i++) {
  214. const lineVisibleRanges = visibleRanges[i];
  215. const lineNumber = lineVisibleRanges.lineNumber;
  216. const lineIndex = lineNumber - visibleStartLineNumber;
  217. const lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight;
  218. const top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0;
  219. let innerCornerOutput = '';
  220. let restOfSelectionOutput = '';
  221. for (let j = 0, lenJ = lineVisibleRanges.ranges.length; j < lenJ; j++) {
  222. const visibleRange = lineVisibleRanges.ranges[j];
  223. if (visibleRangesHaveStyle) {
  224. const startStyle = visibleRange.startStyle;
  225. const endStyle = visibleRange.endStyle;
  226. if (startStyle.top === 1 /* CornerStyle.INTERN */ || startStyle.bottom === 1 /* CornerStyle.INTERN */) {
  227. // Reverse rounded corner to the left
  228. // First comes the selection (blue layer)
  229. innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
  230. // Second comes the background (white layer) with inverse border radius
  231. let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
  232. if (startStyle.top === 1 /* CornerStyle.INTERN */) {
  233. className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
  234. }
  235. if (startStyle.bottom === 1 /* CornerStyle.INTERN */) {
  236. className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
  237. }
  238. innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left - SelectionsOverlay.ROUNDED_PIECE_WIDTH, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
  239. }
  240. if (endStyle.top === 1 /* CornerStyle.INTERN */ || endStyle.bottom === 1 /* CornerStyle.INTERN */) {
  241. // Reverse rounded corner to the right
  242. // First comes the selection (blue layer)
  243. innerCornerOutput += this._createSelectionPiece(top, lineHeight, SelectionsOverlay.SELECTION_CLASS_NAME, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
  244. // Second comes the background (white layer) with inverse border radius
  245. let className = SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME;
  246. if (endStyle.top === 1 /* CornerStyle.INTERN */) {
  247. className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
  248. }
  249. if (endStyle.bottom === 1 /* CornerStyle.INTERN */) {
  250. className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
  251. }
  252. innerCornerOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left + visibleRange.width, SelectionsOverlay.ROUNDED_PIECE_WIDTH);
  253. }
  254. }
  255. let className = SelectionsOverlay.SELECTION_CLASS_NAME;
  256. if (visibleRangesHaveStyle) {
  257. const startStyle = visibleRange.startStyle;
  258. const endStyle = visibleRange.endStyle;
  259. if (startStyle.top === 0 /* CornerStyle.EXTERN */) {
  260. className += ' ' + SelectionsOverlay.SELECTION_TOP_LEFT;
  261. }
  262. if (startStyle.bottom === 0 /* CornerStyle.EXTERN */) {
  263. className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_LEFT;
  264. }
  265. if (endStyle.top === 0 /* CornerStyle.EXTERN */) {
  266. className += ' ' + SelectionsOverlay.SELECTION_TOP_RIGHT;
  267. }
  268. if (endStyle.bottom === 0 /* CornerStyle.EXTERN */) {
  269. className += ' ' + SelectionsOverlay.SELECTION_BOTTOM_RIGHT;
  270. }
  271. }
  272. restOfSelectionOutput += this._createSelectionPiece(top, lineHeight, className, visibleRange.left, visibleRange.width);
  273. }
  274. output2[lineIndex][0] += innerCornerOutput;
  275. output2[lineIndex][1] += restOfSelectionOutput;
  276. }
  277. }
  278. prepareRender(ctx) {
  279. // Build HTML for inner corners separate from HTML for the rest of selections,
  280. // as the inner corner HTML can interfere with that of other selections.
  281. // In final render, make sure to place the inner corner HTML before the rest of selection HTML. See issue #77777.
  282. const output = [];
  283. const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
  284. const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
  285. for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
  286. const lineIndex = lineNumber - visibleStartLineNumber;
  287. output[lineIndex] = ['', ''];
  288. }
  289. const thisFrameVisibleRangesWithStyle = [];
  290. for (let i = 0, len = this._selections.length; i < len; i++) {
  291. const selection = this._selections[i];
  292. if (selection.isEmpty()) {
  293. thisFrameVisibleRangesWithStyle[i] = null;
  294. continue;
  295. }
  296. const visibleRangesWithStyle = this._getVisibleRangesWithStyle(selection, ctx, this._previousFrameVisibleRangesWithStyle[i]);
  297. thisFrameVisibleRangesWithStyle[i] = visibleRangesWithStyle;
  298. this._actualRenderOneSelection(output, visibleStartLineNumber, this._selections.length > 1, visibleRangesWithStyle);
  299. }
  300. this._previousFrameVisibleRangesWithStyle = thisFrameVisibleRangesWithStyle;
  301. this._renderResult = output.map(([internalCorners, restOfSelection]) => internalCorners + restOfSelection);
  302. }
  303. render(startLineNumber, lineNumber) {
  304. if (!this._renderResult) {
  305. return '';
  306. }
  307. const lineIndex = lineNumber - startLineNumber;
  308. if (lineIndex < 0 || lineIndex >= this._renderResult.length) {
  309. return '';
  310. }
  311. return this._renderResult[lineIndex];
  312. }
  313. }
  314. SelectionsOverlay.SELECTION_CLASS_NAME = 'selected-text';
  315. SelectionsOverlay.SELECTION_TOP_LEFT = 'top-left-radius';
  316. SelectionsOverlay.SELECTION_BOTTOM_LEFT = 'bottom-left-radius';
  317. SelectionsOverlay.SELECTION_TOP_RIGHT = 'top-right-radius';
  318. SelectionsOverlay.SELECTION_BOTTOM_RIGHT = 'bottom-right-radius';
  319. SelectionsOverlay.EDITOR_BACKGROUND_CLASS_NAME = 'monaco-editor-background';
  320. SelectionsOverlay.ROUNDED_PIECE_WIDTH = 10;
  321. registerThemingParticipant((theme, collector) => {
  322. const editorSelectionColor = theme.getColor(editorSelectionBackground);
  323. if (editorSelectionColor) {
  324. collector.addRule(`.monaco-editor .focused .selected-text { background-color: ${editorSelectionColor}; }`);
  325. }
  326. const editorInactiveSelectionColor = theme.getColor(editorInactiveSelection);
  327. if (editorInactiveSelectionColor) {
  328. collector.addRule(`.monaco-editor .selected-text { background-color: ${editorInactiveSelectionColor}; }`);
  329. }
  330. const editorSelectionForegroundColor = theme.getColor(editorSelectionForeground);
  331. if (editorSelectionForegroundColor && !editorSelectionForegroundColor.isTransparent()) {
  332. collector.addRule(`.monaco-editor .view-line span.inline-selected-text { color: ${editorSelectionForegroundColor}; }`);
  333. }
  334. });
  335. function abs(n) {
  336. return n < 0 ? -n : n;
  337. }