f74bd006f515c4a8c02000c45dfce009535850d23fc35714bb5ebe2a493a0e34c949155543e870e77da38c4ee67a4aff9c610a71fc8ff6d643308a9cfd4ca3 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  6. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  7. return new (P || (P = Promise))(function (resolve, reject) {
  8. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  9. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  10. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  11. step((generator = generator.apply(thisArg, _arguments || [])).next());
  12. });
  13. };
  14. import * as dom from '../../dom.js';
  15. import { TimeoutTimer } from '../../../common/async.js';
  16. import { CancellationTokenSource } from '../../../common/cancellation.js';
  17. import { isMarkdownString } from '../../../common/htmlContent.js';
  18. import { stripIcons } from '../../../common/iconLabels.js';
  19. import { DisposableStore } from '../../../common/lifecycle.js';
  20. import { isFunction, isString } from '../../../common/types.js';
  21. import { localize } from '../../../../nls.js';
  22. export function setupNativeHover(htmlElement, tooltip) {
  23. if (isString(tooltip)) {
  24. // Icons don't render in the native hover so we strip them out
  25. htmlElement.title = stripIcons(tooltip);
  26. }
  27. else if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.markdownNotSupportedFallback) {
  28. htmlElement.title = tooltip.markdownNotSupportedFallback;
  29. }
  30. else {
  31. htmlElement.removeAttribute('title');
  32. }
  33. }
  34. class UpdatableHoverWidget {
  35. constructor(hoverDelegate, target, fadeInAnimation) {
  36. this.hoverDelegate = hoverDelegate;
  37. this.target = target;
  38. this.fadeInAnimation = fadeInAnimation;
  39. }
  40. update(content, focus, options) {
  41. var _a;
  42. return __awaiter(this, void 0, void 0, function* () {
  43. if (this._cancellationTokenSource) {
  44. // there's an computation ongoing, cancel it
  45. this._cancellationTokenSource.dispose(true);
  46. this._cancellationTokenSource = undefined;
  47. }
  48. if (this.isDisposed) {
  49. return;
  50. }
  51. let resolvedContent;
  52. if (content === undefined || isString(content) || content instanceof HTMLElement) {
  53. resolvedContent = content;
  54. }
  55. else if (!isFunction(content.markdown)) {
  56. resolvedContent = (_a = content.markdown) !== null && _a !== void 0 ? _a : content.markdownNotSupportedFallback;
  57. }
  58. else {
  59. // compute the content, potentially long-running
  60. // show 'Loading' if no hover is up yet
  61. if (!this._hoverWidget) {
  62. this.show(localize('iconLabel.loading', "Loading..."), focus);
  63. }
  64. // compute the content
  65. this._cancellationTokenSource = new CancellationTokenSource();
  66. const token = this._cancellationTokenSource.token;
  67. resolvedContent = yield content.markdown(token);
  68. if (resolvedContent === undefined) {
  69. resolvedContent = content.markdownNotSupportedFallback;
  70. }
  71. if (this.isDisposed || token.isCancellationRequested) {
  72. // either the widget has been closed in the meantime
  73. // or there has been a new call to `update`
  74. return;
  75. }
  76. }
  77. this.show(resolvedContent, focus, options);
  78. });
  79. }
  80. show(content, focus, options) {
  81. const oldHoverWidget = this._hoverWidget;
  82. if (this.hasContent(content)) {
  83. const hoverOptions = Object.assign({ content, target: this.target, showPointer: this.hoverDelegate.placement === 'element', hoverPosition: 2 /* HoverPosition.BELOW */, skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget }, options);
  84. this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
  85. }
  86. oldHoverWidget === null || oldHoverWidget === void 0 ? void 0 : oldHoverWidget.dispose();
  87. }
  88. hasContent(content) {
  89. if (!content) {
  90. return false;
  91. }
  92. if (isMarkdownString(content)) {
  93. return !!content.value;
  94. }
  95. return true;
  96. }
  97. get isDisposed() {
  98. var _a;
  99. return (_a = this._hoverWidget) === null || _a === void 0 ? void 0 : _a.isDisposed;
  100. }
  101. dispose() {
  102. var _a, _b;
  103. (_a = this._hoverWidget) === null || _a === void 0 ? void 0 : _a.dispose();
  104. (_b = this._cancellationTokenSource) === null || _b === void 0 ? void 0 : _b.dispose(true);
  105. this._cancellationTokenSource = undefined;
  106. }
  107. }
  108. export function setupCustomHover(hoverDelegate, htmlElement, content, options) {
  109. let hoverPreparation;
  110. let hoverWidget;
  111. const hideHover = (disposeWidget, disposePreparation) => {
  112. var _a;
  113. if (disposeWidget) {
  114. hoverWidget === null || hoverWidget === void 0 ? void 0 : hoverWidget.dispose();
  115. hoverWidget = undefined;
  116. }
  117. if (disposePreparation) {
  118. hoverPreparation === null || hoverPreparation === void 0 ? void 0 : hoverPreparation.dispose();
  119. hoverPreparation = undefined;
  120. }
  121. (_a = hoverDelegate.onDidHideHover) === null || _a === void 0 ? void 0 : _a.call(hoverDelegate);
  122. };
  123. const triggerShowHover = (delay, focus, target) => {
  124. return new TimeoutTimer(() => __awaiter(this, void 0, void 0, function* () {
  125. if (!hoverWidget || hoverWidget.isDisposed) {
  126. hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0);
  127. yield hoverWidget.update(content, focus, options);
  128. }
  129. }), delay);
  130. };
  131. const onMouseOver = () => {
  132. if (hoverPreparation) {
  133. return;
  134. }
  135. const toDispose = new DisposableStore();
  136. const onMouseLeave = (e) => hideHover(false, e.fromElement === htmlElement);
  137. toDispose.add(dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_LEAVE, onMouseLeave, true));
  138. const onMouseDown = () => hideHover(true, true);
  139. toDispose.add(dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_DOWN, onMouseDown, true));
  140. const target = {
  141. targetElements: [htmlElement],
  142. dispose: () => { }
  143. };
  144. if (hoverDelegate.placement === undefined || hoverDelegate.placement === 'mouse') {
  145. // track the mouse position
  146. const onMouseMove = (e) => {
  147. target.x = e.x + 10;
  148. if ((e.target instanceof HTMLElement) && e.target.classList.contains('action-label')) {
  149. hideHover(true, true);
  150. }
  151. };
  152. toDispose.add(dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_MOVE, onMouseMove, true));
  153. }
  154. toDispose.add(triggerShowHover(hoverDelegate.delay, false, target));
  155. hoverPreparation = toDispose;
  156. };
  157. const mouseOverDomEmitter = dom.addDisposableListener(htmlElement, dom.EventType.MOUSE_OVER, onMouseOver, true);
  158. const hover = {
  159. show: focus => {
  160. hideHover(false, true); // terminate a ongoing mouse over preparation
  161. triggerShowHover(0, focus); // show hover immediately
  162. },
  163. hide: () => {
  164. hideHover(true, true);
  165. },
  166. update: (newContent, hoverOptions) => __awaiter(this, void 0, void 0, function* () {
  167. content = newContent;
  168. yield (hoverWidget === null || hoverWidget === void 0 ? void 0 : hoverWidget.update(content, undefined, hoverOptions));
  169. }),
  170. dispose: () => {
  171. mouseOverDomEmitter.dispose();
  172. hideHover(true, true);
  173. }
  174. };
  175. return hover;
  176. }