lightBulbWidget.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. import * as dom from '../../../base/browser/dom.js';
  15. import { GlobalMouseMoveMonitor, standardMouseMoveMerger } from '../../../base/browser/globalMouseMoveMonitor.js';
  16. import { Gesture } from '../../../base/browser/touch.js';
  17. import { Codicon } from '../../../base/common/codicons.js';
  18. import { Emitter } from '../../../base/common/event.js';
  19. import { Disposable } from '../../../base/common/lifecycle.js';
  20. import './lightBulbWidget.css';
  21. import { TextModel } from '../../common/model/textModel.js';
  22. import * as nls from '../../../nls.js';
  23. import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
  24. import { editorBackground, editorLightBulbAutoFixForeground, editorLightBulbForeground } from '../../../platform/theme/common/colorRegistry.js';
  25. import { registerThemingParticipant } from '../../../platform/theme/common/themeService.js';
  26. var LightBulbState;
  27. (function (LightBulbState) {
  28. LightBulbState.Hidden = { type: 0 /* Hidden */ };
  29. class Showing {
  30. constructor(actions, trigger, editorPosition, widgetPosition) {
  31. this.actions = actions;
  32. this.trigger = trigger;
  33. this.editorPosition = editorPosition;
  34. this.widgetPosition = widgetPosition;
  35. this.type = 1 /* Showing */;
  36. }
  37. }
  38. LightBulbState.Showing = Showing;
  39. })(LightBulbState || (LightBulbState = {}));
  40. let LightBulbWidget = class LightBulbWidget extends Disposable {
  41. constructor(_editor, _quickFixActionId, _preferredFixActionId, _keybindingService) {
  42. super();
  43. this._editor = _editor;
  44. this._quickFixActionId = _quickFixActionId;
  45. this._preferredFixActionId = _preferredFixActionId;
  46. this._keybindingService = _keybindingService;
  47. this._onClick = this._register(new Emitter());
  48. this.onClick = this._onClick.event;
  49. this._state = LightBulbState.Hidden;
  50. this._domNode = document.createElement('div');
  51. this._domNode.className = Codicon.lightBulb.classNames;
  52. this._editor.addContentWidget(this);
  53. this._register(this._editor.onDidChangeModelContent(_ => {
  54. // cancel when the line in question has been removed
  55. const editorModel = this._editor.getModel();
  56. if (this.state.type !== 1 /* Showing */ || !editorModel || this.state.editorPosition.lineNumber >= editorModel.getLineCount()) {
  57. this.hide();
  58. }
  59. }));
  60. Gesture.ignoreTarget(this._domNode);
  61. this._register(dom.addStandardDisposableGenericMouseDownListner(this._domNode, e => {
  62. if (this.state.type !== 1 /* Showing */) {
  63. return;
  64. }
  65. // Make sure that focus / cursor location is not lost when clicking widget icon
  66. this._editor.focus();
  67. e.preventDefault();
  68. // a bit of extra work to make sure the menu
  69. // doesn't cover the line-text
  70. const { top, height } = dom.getDomNodePagePosition(this._domNode);
  71. const lineHeight = this._editor.getOption(58 /* lineHeight */);
  72. let pad = Math.floor(lineHeight / 3);
  73. if (this.state.widgetPosition.position !== null && this.state.widgetPosition.position.lineNumber < this.state.editorPosition.lineNumber) {
  74. pad += lineHeight;
  75. }
  76. this._onClick.fire({
  77. x: e.posx,
  78. y: top + height + pad,
  79. actions: this.state.actions,
  80. trigger: this.state.trigger,
  81. });
  82. }));
  83. this._register(dom.addDisposableListener(this._domNode, 'mouseenter', (e) => {
  84. if ((e.buttons & 1) !== 1) {
  85. return;
  86. }
  87. // mouse enters lightbulb while the primary/left button
  88. // is being pressed -> hide the lightbulb and block future
  89. // showings until mouse is released
  90. this.hide();
  91. const monitor = new GlobalMouseMoveMonitor();
  92. monitor.startMonitoring(e.target, e.buttons, standardMouseMoveMerger, () => { }, () => {
  93. monitor.dispose();
  94. });
  95. }));
  96. this._register(this._editor.onDidChangeConfiguration(e => {
  97. // hide when told to do so
  98. if (e.hasChanged(56 /* lightbulb */) && !this._editor.getOption(56 /* lightbulb */).enabled) {
  99. this.hide();
  100. }
  101. }));
  102. this._updateLightBulbTitleAndIcon();
  103. this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitleAndIcon, this));
  104. }
  105. dispose() {
  106. super.dispose();
  107. this._editor.removeContentWidget(this);
  108. }
  109. getId() {
  110. return 'LightBulbWidget';
  111. }
  112. getDomNode() {
  113. return this._domNode;
  114. }
  115. getPosition() {
  116. return this._state.type === 1 /* Showing */ ? this._state.widgetPosition : null;
  117. }
  118. update(actions, trigger, atPosition) {
  119. if (actions.validActions.length <= 0) {
  120. return this.hide();
  121. }
  122. const options = this._editor.getOptions();
  123. if (!options.get(56 /* lightbulb */).enabled) {
  124. return this.hide();
  125. }
  126. const model = this._editor.getModel();
  127. if (!model) {
  128. return this.hide();
  129. }
  130. const { lineNumber, column } = model.validatePosition(atPosition);
  131. const tabSize = model.getOptions().tabSize;
  132. const fontInfo = options.get(43 /* fontInfo */);
  133. const lineContent = model.getLineContent(lineNumber);
  134. const indent = TextModel.computeIndentLevel(lineContent, tabSize);
  135. const lineHasSpace = fontInfo.spaceWidth * indent > 22;
  136. const isFolded = (lineNumber) => {
  137. return lineNumber > 2 && this._editor.getTopForLineNumber(lineNumber) === this._editor.getTopForLineNumber(lineNumber - 1);
  138. };
  139. let effectiveLineNumber = lineNumber;
  140. if (!lineHasSpace) {
  141. if (lineNumber > 1 && !isFolded(lineNumber - 1)) {
  142. effectiveLineNumber -= 1;
  143. }
  144. else if (!isFolded(lineNumber + 1)) {
  145. effectiveLineNumber += 1;
  146. }
  147. else if (column * fontInfo.spaceWidth < 22) {
  148. // cannot show lightbulb above/below and showing
  149. // it inline would overlay the cursor...
  150. return this.hide();
  151. }
  152. }
  153. this.state = new LightBulbState.Showing(actions, trigger, atPosition, {
  154. position: { lineNumber: effectiveLineNumber, column: 1 },
  155. preference: LightBulbWidget._posPref
  156. });
  157. this._editor.layoutContentWidget(this);
  158. }
  159. hide() {
  160. this.state = LightBulbState.Hidden;
  161. this._editor.layoutContentWidget(this);
  162. }
  163. get state() { return this._state; }
  164. set state(value) {
  165. this._state = value;
  166. this._updateLightBulbTitleAndIcon();
  167. }
  168. _updateLightBulbTitleAndIcon() {
  169. if (this.state.type === 1 /* Showing */ && this.state.actions.hasAutoFix) {
  170. // update icon
  171. this._domNode.classList.remove(...Codicon.lightBulb.classNamesArray);
  172. this._domNode.classList.add(...Codicon.lightbulbAutofix.classNamesArray);
  173. const preferredKb = this._keybindingService.lookupKeybinding(this._preferredFixActionId);
  174. if (preferredKb) {
  175. this.title = nls.localize('preferredcodeActionWithKb', "Show Code Actions. Preferred Quick Fix Available ({0})", preferredKb.getLabel());
  176. return;
  177. }
  178. }
  179. // update icon
  180. this._domNode.classList.remove(...Codicon.lightbulbAutofix.classNamesArray);
  181. this._domNode.classList.add(...Codicon.lightBulb.classNamesArray);
  182. const kb = this._keybindingService.lookupKeybinding(this._quickFixActionId);
  183. if (kb) {
  184. this.title = nls.localize('codeActionWithKb', "Show Code Actions ({0})", kb.getLabel());
  185. }
  186. else {
  187. this.title = nls.localize('codeAction', "Show Code Actions");
  188. }
  189. }
  190. set title(value) {
  191. this._domNode.title = value;
  192. }
  193. };
  194. LightBulbWidget._posPref = [0 /* EXACT */];
  195. LightBulbWidget = __decorate([
  196. __param(3, IKeybindingService)
  197. ], LightBulbWidget);
  198. export { LightBulbWidget };
  199. registerThemingParticipant((theme, collector) => {
  200. var _a;
  201. const editorBackgroundColor = (_a = theme.getColor(editorBackground)) === null || _a === void 0 ? void 0 : _a.transparent(0.7);
  202. // Lightbulb Icon
  203. const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground);
  204. if (editorLightBulbForegroundColor) {
  205. collector.addRule(`
  206. .monaco-editor .contentWidgets ${Codicon.lightBulb.cssSelector} {
  207. color: ${editorLightBulbForegroundColor};
  208. background-color: ${editorBackgroundColor};
  209. }`);
  210. }
  211. // Lightbulb Auto Fix Icon
  212. const editorLightBulbAutoFixForegroundColor = theme.getColor(editorLightBulbAutoFixForeground);
  213. if (editorLightBulbAutoFixForegroundColor) {
  214. collector.addRule(`
  215. .monaco-editor .contentWidgets ${Codicon.lightbulbAutofix.cssSelector} {
  216. color: ${editorLightBulbAutoFixForegroundColor};
  217. background-color: ${editorBackgroundColor};
  218. }`);
  219. }
  220. });