427907dc3ccf0c149005057b354bd2c0174d256c5e75e9b39fa9cbeac84026cb7028c1d33075c7de6c2b7141998ee02bdce643dcac9053f0124a83c499c26a 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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 './viewCursors.css';
  6. import { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
  7. import { IntervalTimer, TimeoutTimer } from '../../../../base/common/async.js';
  8. import { ViewPart } from '../../view/viewPart.js';
  9. import { ViewCursor } from './viewCursor.js';
  10. import { TextEditorCursorStyle } from '../../../common/config/editorOptions.js';
  11. import { editorCursorBackground, editorCursorForeground } from '../../../common/core/editorColorRegistry.js';
  12. import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';
  13. import { isHighContrast } from '../../../../platform/theme/common/theme.js';
  14. export class ViewCursors extends ViewPart {
  15. constructor(context) {
  16. super(context);
  17. const options = this._context.configuration.options;
  18. this._readOnly = options.get(83 /* EditorOption.readOnly */);
  19. this._cursorBlinking = options.get(22 /* EditorOption.cursorBlinking */);
  20. this._cursorStyle = options.get(24 /* EditorOption.cursorStyle */);
  21. this._cursorSmoothCaretAnimation = options.get(23 /* EditorOption.cursorSmoothCaretAnimation */);
  22. this._selectionIsEmpty = true;
  23. this._isComposingInput = false;
  24. this._isVisible = false;
  25. this._primaryCursor = new ViewCursor(this._context);
  26. this._secondaryCursors = [];
  27. this._renderData = [];
  28. this._domNode = createFastDomNode(document.createElement('div'));
  29. this._domNode.setAttribute('role', 'presentation');
  30. this._domNode.setAttribute('aria-hidden', 'true');
  31. this._updateDomClassName();
  32. this._domNode.appendChild(this._primaryCursor.getDomNode());
  33. this._startCursorBlinkAnimation = new TimeoutTimer();
  34. this._cursorFlatBlinkInterval = new IntervalTimer();
  35. this._blinkingEnabled = false;
  36. this._editorHasFocus = false;
  37. this._updateBlinking();
  38. }
  39. dispose() {
  40. super.dispose();
  41. this._startCursorBlinkAnimation.dispose();
  42. this._cursorFlatBlinkInterval.dispose();
  43. }
  44. getDomNode() {
  45. return this._domNode;
  46. }
  47. // --- begin event handlers
  48. onCompositionStart(e) {
  49. this._isComposingInput = true;
  50. this._updateBlinking();
  51. return true;
  52. }
  53. onCompositionEnd(e) {
  54. this._isComposingInput = false;
  55. this._updateBlinking();
  56. return true;
  57. }
  58. onConfigurationChanged(e) {
  59. const options = this._context.configuration.options;
  60. this._readOnly = options.get(83 /* EditorOption.readOnly */);
  61. this._cursorBlinking = options.get(22 /* EditorOption.cursorBlinking */);
  62. this._cursorStyle = options.get(24 /* EditorOption.cursorStyle */);
  63. this._cursorSmoothCaretAnimation = options.get(23 /* EditorOption.cursorSmoothCaretAnimation */);
  64. this._updateBlinking();
  65. this._updateDomClassName();
  66. this._primaryCursor.onConfigurationChanged(e);
  67. for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
  68. this._secondaryCursors[i].onConfigurationChanged(e);
  69. }
  70. return true;
  71. }
  72. _onCursorPositionChanged(position, secondaryPositions) {
  73. this._primaryCursor.onCursorPositionChanged(position);
  74. this._updateBlinking();
  75. if (this._secondaryCursors.length < secondaryPositions.length) {
  76. // Create new cursors
  77. const addCnt = secondaryPositions.length - this._secondaryCursors.length;
  78. for (let i = 0; i < addCnt; i++) {
  79. const newCursor = new ViewCursor(this._context);
  80. this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
  81. this._secondaryCursors.push(newCursor);
  82. }
  83. }
  84. else if (this._secondaryCursors.length > secondaryPositions.length) {
  85. // Remove some cursors
  86. const removeCnt = this._secondaryCursors.length - secondaryPositions.length;
  87. for (let i = 0; i < removeCnt; i++) {
  88. this._domNode.removeChild(this._secondaryCursors[0].getDomNode());
  89. this._secondaryCursors.splice(0, 1);
  90. }
  91. }
  92. for (let i = 0; i < secondaryPositions.length; i++) {
  93. this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i]);
  94. }
  95. }
  96. onCursorStateChanged(e) {
  97. const positions = [];
  98. for (let i = 0, len = e.selections.length; i < len; i++) {
  99. positions[i] = e.selections[i].getPosition();
  100. }
  101. this._onCursorPositionChanged(positions[0], positions.slice(1));
  102. const selectionIsEmpty = e.selections[0].isEmpty();
  103. if (this._selectionIsEmpty !== selectionIsEmpty) {
  104. this._selectionIsEmpty = selectionIsEmpty;
  105. this._updateDomClassName();
  106. }
  107. return true;
  108. }
  109. onDecorationsChanged(e) {
  110. // true for inline decorations that can end up relayouting text
  111. return true;
  112. }
  113. onFlushed(e) {
  114. return true;
  115. }
  116. onFocusChanged(e) {
  117. this._editorHasFocus = e.isFocused;
  118. this._updateBlinking();
  119. return false;
  120. }
  121. onLinesChanged(e) {
  122. return true;
  123. }
  124. onLinesDeleted(e) {
  125. return true;
  126. }
  127. onLinesInserted(e) {
  128. return true;
  129. }
  130. onScrollChanged(e) {
  131. return true;
  132. }
  133. onTokensChanged(e) {
  134. const shouldRender = (position) => {
  135. for (let i = 0, len = e.ranges.length; i < len; i++) {
  136. if (e.ranges[i].fromLineNumber <= position.lineNumber && position.lineNumber <= e.ranges[i].toLineNumber) {
  137. return true;
  138. }
  139. }
  140. return false;
  141. };
  142. if (shouldRender(this._primaryCursor.getPosition())) {
  143. return true;
  144. }
  145. for (const secondaryCursor of this._secondaryCursors) {
  146. if (shouldRender(secondaryCursor.getPosition())) {
  147. return true;
  148. }
  149. }
  150. return false;
  151. }
  152. onZonesChanged(e) {
  153. return true;
  154. }
  155. // --- end event handlers
  156. // ---- blinking logic
  157. _getCursorBlinking() {
  158. if (this._isComposingInput) {
  159. // avoid double cursors
  160. return 0 /* TextEditorCursorBlinkingStyle.Hidden */;
  161. }
  162. if (!this._editorHasFocus) {
  163. return 0 /* TextEditorCursorBlinkingStyle.Hidden */;
  164. }
  165. if (this._readOnly) {
  166. return 5 /* TextEditorCursorBlinkingStyle.Solid */;
  167. }
  168. return this._cursorBlinking;
  169. }
  170. _updateBlinking() {
  171. this._startCursorBlinkAnimation.cancel();
  172. this._cursorFlatBlinkInterval.cancel();
  173. const blinkingStyle = this._getCursorBlinking();
  174. // hidden and solid are special as they involve no animations
  175. const isHidden = (blinkingStyle === 0 /* TextEditorCursorBlinkingStyle.Hidden */);
  176. const isSolid = (blinkingStyle === 5 /* TextEditorCursorBlinkingStyle.Solid */);
  177. if (isHidden) {
  178. this._hide();
  179. }
  180. else {
  181. this._show();
  182. }
  183. this._blinkingEnabled = false;
  184. this._updateDomClassName();
  185. if (!isHidden && !isSolid) {
  186. if (blinkingStyle === 1 /* TextEditorCursorBlinkingStyle.Blink */) {
  187. // flat blinking is handled by JavaScript to save battery life due to Chromium step timing issue https://bugs.chromium.org/p/chromium/issues/detail?id=361587
  188. this._cursorFlatBlinkInterval.cancelAndSet(() => {
  189. if (this._isVisible) {
  190. this._hide();
  191. }
  192. else {
  193. this._show();
  194. }
  195. }, ViewCursors.BLINK_INTERVAL);
  196. }
  197. else {
  198. this._startCursorBlinkAnimation.setIfNotSet(() => {
  199. this._blinkingEnabled = true;
  200. this._updateDomClassName();
  201. }, ViewCursors.BLINK_INTERVAL);
  202. }
  203. }
  204. }
  205. // --- end blinking logic
  206. _updateDomClassName() {
  207. this._domNode.setClassName(this._getClassName());
  208. }
  209. _getClassName() {
  210. let result = 'cursors-layer';
  211. if (!this._selectionIsEmpty) {
  212. result += ' has-selection';
  213. }
  214. switch (this._cursorStyle) {
  215. case TextEditorCursorStyle.Line:
  216. result += ' cursor-line-style';
  217. break;
  218. case TextEditorCursorStyle.Block:
  219. result += ' cursor-block-style';
  220. break;
  221. case TextEditorCursorStyle.Underline:
  222. result += ' cursor-underline-style';
  223. break;
  224. case TextEditorCursorStyle.LineThin:
  225. result += ' cursor-line-thin-style';
  226. break;
  227. case TextEditorCursorStyle.BlockOutline:
  228. result += ' cursor-block-outline-style';
  229. break;
  230. case TextEditorCursorStyle.UnderlineThin:
  231. result += ' cursor-underline-thin-style';
  232. break;
  233. default:
  234. result += ' cursor-line-style';
  235. }
  236. if (this._blinkingEnabled) {
  237. switch (this._getCursorBlinking()) {
  238. case 1 /* TextEditorCursorBlinkingStyle.Blink */:
  239. result += ' cursor-blink';
  240. break;
  241. case 2 /* TextEditorCursorBlinkingStyle.Smooth */:
  242. result += ' cursor-smooth';
  243. break;
  244. case 3 /* TextEditorCursorBlinkingStyle.Phase */:
  245. result += ' cursor-phase';
  246. break;
  247. case 4 /* TextEditorCursorBlinkingStyle.Expand */:
  248. result += ' cursor-expand';
  249. break;
  250. case 5 /* TextEditorCursorBlinkingStyle.Solid */:
  251. result += ' cursor-solid';
  252. break;
  253. default:
  254. result += ' cursor-solid';
  255. }
  256. }
  257. else {
  258. result += ' cursor-solid';
  259. }
  260. if (this._cursorSmoothCaretAnimation) {
  261. result += ' cursor-smooth-caret-animation';
  262. }
  263. return result;
  264. }
  265. _show() {
  266. this._primaryCursor.show();
  267. for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
  268. this._secondaryCursors[i].show();
  269. }
  270. this._isVisible = true;
  271. }
  272. _hide() {
  273. this._primaryCursor.hide();
  274. for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
  275. this._secondaryCursors[i].hide();
  276. }
  277. this._isVisible = false;
  278. }
  279. // ---- IViewPart implementation
  280. prepareRender(ctx) {
  281. this._primaryCursor.prepareRender(ctx);
  282. for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
  283. this._secondaryCursors[i].prepareRender(ctx);
  284. }
  285. }
  286. render(ctx) {
  287. const renderData = [];
  288. let renderDataLen = 0;
  289. const primaryRenderData = this._primaryCursor.render(ctx);
  290. if (primaryRenderData) {
  291. renderData[renderDataLen++] = primaryRenderData;
  292. }
  293. for (let i = 0, len = this._secondaryCursors.length; i < len; i++) {
  294. const secondaryRenderData = this._secondaryCursors[i].render(ctx);
  295. if (secondaryRenderData) {
  296. renderData[renderDataLen++] = secondaryRenderData;
  297. }
  298. }
  299. this._renderData = renderData;
  300. }
  301. getLastRenderData() {
  302. return this._renderData;
  303. }
  304. }
  305. ViewCursors.BLINK_INTERVAL = 500;
  306. registerThemingParticipant((theme, collector) => {
  307. const caret = theme.getColor(editorCursorForeground);
  308. if (caret) {
  309. let caretBackground = theme.getColor(editorCursorBackground);
  310. if (!caretBackground) {
  311. caretBackground = caret.opposite();
  312. }
  313. collector.addRule(`.monaco-editor .inputarea.ime-input { caret-color: ${caret}; }`);
  314. collector.addRule(`.monaco-editor .cursors-layer .cursor { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`);
  315. if (isHighContrast(theme.type)) {
  316. collector.addRule(`.monaco-editor .cursors-layer.has-selection .cursor { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`);
  317. }
  318. }
  319. });