suggestWidgetDetails.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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 { isSafari } from '../../../base/browser/browser.js';
  15. import * as dom from '../../../base/browser/dom.js';
  16. import { DomScrollableElement } from '../../../base/browser/ui/scrollbar/scrollableElement.js';
  17. import { Codicon } from '../../../base/common/codicons.js';
  18. import { Emitter } from '../../../base/common/event.js';
  19. import { MarkdownString } from '../../../base/common/htmlContent.js';
  20. import { DisposableStore } from '../../../base/common/lifecycle.js';
  21. import { MarkdownRenderer } from '../../browser/core/markdownRenderer.js';
  22. import { EDITOR_FONT_DEFAULTS } from '../../common/config/editorOptions.js';
  23. import { ResizableHTMLElement } from './resizable.js';
  24. import * as nls from '../../../nls.js';
  25. import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
  26. export function canExpandCompletionItem(item) {
  27. return !!item && Boolean(item.completion.documentation || item.completion.detail && item.completion.detail !== item.completion.label);
  28. }
  29. let SuggestDetailsWidget = class SuggestDetailsWidget {
  30. constructor(_editor, instaService) {
  31. this._editor = _editor;
  32. this._onDidClose = new Emitter();
  33. this.onDidClose = this._onDidClose.event;
  34. this._onDidChangeContents = new Emitter();
  35. this.onDidChangeContents = this._onDidChangeContents.event;
  36. this._disposables = new DisposableStore();
  37. this._renderDisposeable = new DisposableStore();
  38. this._borderWidth = 1;
  39. this._size = new dom.Dimension(330, 0);
  40. this.domNode = dom.$('.suggest-details');
  41. this.domNode.classList.add('no-docs');
  42. this._markdownRenderer = instaService.createInstance(MarkdownRenderer, { editor: _editor });
  43. this._body = dom.$('.body');
  44. this._scrollbar = new DomScrollableElement(this._body, {});
  45. dom.append(this.domNode, this._scrollbar.getDomNode());
  46. this._disposables.add(this._scrollbar);
  47. this._header = dom.append(this._body, dom.$('.header'));
  48. this._close = dom.append(this._header, dom.$('span' + Codicon.close.cssSelector));
  49. this._close.title = nls.localize('details.close', "Close");
  50. this._type = dom.append(this._header, dom.$('p.type'));
  51. this._docs = dom.append(this._body, dom.$('p.docs'));
  52. this._configureFont();
  53. this._disposables.add(this._editor.onDidChangeConfiguration(e => {
  54. if (e.hasChanged(43 /* fontInfo */)) {
  55. this._configureFont();
  56. }
  57. }));
  58. }
  59. dispose() {
  60. this._disposables.dispose();
  61. this._renderDisposeable.dispose();
  62. }
  63. _configureFont() {
  64. const options = this._editor.getOptions();
  65. const fontInfo = options.get(43 /* fontInfo */);
  66. const fontFamily = fontInfo.getMassagedFontFamily(isSafari ? EDITOR_FONT_DEFAULTS.fontFamily : null);
  67. const fontSize = options.get(106 /* suggestFontSize */) || fontInfo.fontSize;
  68. const lineHeight = options.get(107 /* suggestLineHeight */) || fontInfo.lineHeight;
  69. const fontWeight = fontInfo.fontWeight;
  70. const fontSizePx = `${fontSize}px`;
  71. const lineHeightPx = `${lineHeight}px`;
  72. this.domNode.style.fontSize = fontSizePx;
  73. this.domNode.style.lineHeight = `${lineHeight / fontSize}`;
  74. this.domNode.style.fontWeight = fontWeight;
  75. this.domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings;
  76. this._type.style.fontFamily = fontFamily;
  77. this._close.style.height = lineHeightPx;
  78. this._close.style.width = lineHeightPx;
  79. }
  80. getLayoutInfo() {
  81. const lineHeight = this._editor.getOption(107 /* suggestLineHeight */) || this._editor.getOption(43 /* fontInfo */).lineHeight;
  82. const borderWidth = this._borderWidth;
  83. const borderHeight = borderWidth * 2;
  84. return {
  85. lineHeight,
  86. borderWidth,
  87. borderHeight,
  88. verticalPadding: 22,
  89. horizontalPadding: 14
  90. };
  91. }
  92. renderLoading() {
  93. this._type.textContent = nls.localize('loading', "Loading...");
  94. this._docs.textContent = '';
  95. this.domNode.classList.remove('no-docs', 'no-type');
  96. this.layout(this.size.width, this.getLayoutInfo().lineHeight * 2);
  97. this._onDidChangeContents.fire(this);
  98. }
  99. renderItem(item, explainMode) {
  100. var _a, _b;
  101. this._renderDisposeable.clear();
  102. let { detail, documentation } = item.completion;
  103. if (explainMode) {
  104. let md = '';
  105. md += `score: ${item.score[0]}\n`;
  106. md += `prefix: ${(_a = item.word) !== null && _a !== void 0 ? _a : '(no prefix)'}\n`;
  107. md += `word: ${item.completion.filterText ? item.completion.filterText + ' (filterText)' : item.textLabel}\n`;
  108. md += `distance: ${item.distance} (localityBonus-setting)\n`;
  109. md += `index: ${item.idx}, based on ${item.completion.sortText && `sortText: "${item.completion.sortText}"` || 'label'}\n`;
  110. md += `commit_chars: ${(_b = item.completion.commitCharacters) === null || _b === void 0 ? void 0 : _b.join('')}\n`;
  111. documentation = new MarkdownString().appendCodeblock('empty', md);
  112. detail = `Provider: ${item.provider._debugDisplayName}`;
  113. }
  114. if (!explainMode && !canExpandCompletionItem(item)) {
  115. this.clearContents();
  116. return;
  117. }
  118. this.domNode.classList.remove('no-docs', 'no-type');
  119. // --- details
  120. if (detail) {
  121. const cappedDetail = detail.length > 100000 ? `${detail.substr(0, 100000)}…` : detail;
  122. this._type.textContent = cappedDetail;
  123. this._type.title = cappedDetail;
  124. dom.show(this._type);
  125. this._type.classList.toggle('auto-wrap', !/\r?\n^\s+/gmi.test(cappedDetail));
  126. }
  127. else {
  128. dom.clearNode(this._type);
  129. this._type.title = '';
  130. dom.hide(this._type);
  131. this.domNode.classList.add('no-type');
  132. }
  133. // --- documentation
  134. dom.clearNode(this._docs);
  135. if (typeof documentation === 'string') {
  136. this._docs.classList.remove('markdown-docs');
  137. this._docs.textContent = documentation;
  138. }
  139. else if (documentation) {
  140. this._docs.classList.add('markdown-docs');
  141. dom.clearNode(this._docs);
  142. const renderedContents = this._markdownRenderer.render(documentation);
  143. this._docs.appendChild(renderedContents.element);
  144. this._renderDisposeable.add(renderedContents);
  145. this._renderDisposeable.add(this._markdownRenderer.onDidRenderAsync(() => {
  146. this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight);
  147. this._onDidChangeContents.fire(this);
  148. }));
  149. }
  150. this.domNode.style.userSelect = 'text';
  151. this.domNode.tabIndex = -1;
  152. this._close.onmousedown = e => {
  153. e.preventDefault();
  154. e.stopPropagation();
  155. };
  156. this._close.onclick = e => {
  157. e.preventDefault();
  158. e.stopPropagation();
  159. this._onDidClose.fire();
  160. };
  161. this._body.scrollTop = 0;
  162. this.layout(this._size.width, this._type.clientHeight + this._docs.clientHeight);
  163. this._onDidChangeContents.fire(this);
  164. }
  165. clearContents() {
  166. this.domNode.classList.add('no-docs');
  167. this._type.textContent = '';
  168. this._docs.textContent = '';
  169. }
  170. get size() {
  171. return this._size;
  172. }
  173. layout(width, height) {
  174. const newSize = new dom.Dimension(width, height);
  175. if (!dom.Dimension.equals(newSize, this._size)) {
  176. this._size = newSize;
  177. dom.size(this.domNode, width, height);
  178. }
  179. this._scrollbar.scanDomNode();
  180. }
  181. scrollDown(much = 8) {
  182. this._body.scrollTop += much;
  183. }
  184. scrollUp(much = 8) {
  185. this._body.scrollTop -= much;
  186. }
  187. scrollTop() {
  188. this._body.scrollTop = 0;
  189. }
  190. scrollBottom() {
  191. this._body.scrollTop = this._body.scrollHeight;
  192. }
  193. pageDown() {
  194. this.scrollDown(80);
  195. }
  196. pageUp() {
  197. this.scrollUp(80);
  198. }
  199. set borderWidth(width) {
  200. this._borderWidth = width;
  201. }
  202. get borderWidth() {
  203. return this._borderWidth;
  204. }
  205. };
  206. SuggestDetailsWidget = __decorate([
  207. __param(1, IInstantiationService)
  208. ], SuggestDetailsWidget);
  209. export { SuggestDetailsWidget };
  210. export class SuggestDetailsOverlay {
  211. constructor(widget, _editor) {
  212. this.widget = widget;
  213. this._editor = _editor;
  214. this._disposables = new DisposableStore();
  215. this._added = false;
  216. this._preferAlignAtTop = true;
  217. this._resizable = new ResizableHTMLElement();
  218. this._resizable.domNode.classList.add('suggest-details-container');
  219. this._resizable.domNode.appendChild(widget.domNode);
  220. this._resizable.enableSashes(false, true, true, false);
  221. let topLeftNow;
  222. let sizeNow;
  223. let deltaTop = 0;
  224. let deltaLeft = 0;
  225. this._disposables.add(this._resizable.onDidWillResize(() => {
  226. topLeftNow = this._topLeft;
  227. sizeNow = this._resizable.size;
  228. }));
  229. this._disposables.add(this._resizable.onDidResize(e => {
  230. if (topLeftNow && sizeNow) {
  231. this.widget.layout(e.dimension.width, e.dimension.height);
  232. let updateTopLeft = false;
  233. if (e.west) {
  234. deltaLeft = sizeNow.width - e.dimension.width;
  235. updateTopLeft = true;
  236. }
  237. if (e.north) {
  238. deltaTop = sizeNow.height - e.dimension.height;
  239. updateTopLeft = true;
  240. }
  241. if (updateTopLeft) {
  242. this._applyTopLeft({
  243. top: topLeftNow.top + deltaTop,
  244. left: topLeftNow.left + deltaLeft,
  245. });
  246. }
  247. }
  248. if (e.done) {
  249. topLeftNow = undefined;
  250. sizeNow = undefined;
  251. deltaTop = 0;
  252. deltaLeft = 0;
  253. this._userSize = e.dimension;
  254. }
  255. }));
  256. this._disposables.add(this.widget.onDidChangeContents(() => {
  257. var _a;
  258. if (this._anchorBox) {
  259. this._placeAtAnchor(this._anchorBox, (_a = this._userSize) !== null && _a !== void 0 ? _a : this.widget.size, this._preferAlignAtTop);
  260. }
  261. }));
  262. }
  263. dispose() {
  264. this._resizable.dispose();
  265. this._disposables.dispose();
  266. this.hide();
  267. }
  268. getId() {
  269. return 'suggest.details';
  270. }
  271. getDomNode() {
  272. return this._resizable.domNode;
  273. }
  274. getPosition() {
  275. return null;
  276. }
  277. show() {
  278. if (!this._added) {
  279. this._editor.addOverlayWidget(this);
  280. this.getDomNode().style.position = 'fixed';
  281. this._added = true;
  282. }
  283. }
  284. hide(sessionEnded = false) {
  285. this._resizable.clearSashHoverState();
  286. if (this._added) {
  287. this._editor.removeOverlayWidget(this);
  288. this._added = false;
  289. this._anchorBox = undefined;
  290. this._topLeft = undefined;
  291. }
  292. if (sessionEnded) {
  293. this._userSize = undefined;
  294. this.widget.clearContents();
  295. }
  296. }
  297. placeAtAnchor(anchor, preferAlignAtTop) {
  298. var _a;
  299. const anchorBox = anchor.getBoundingClientRect();
  300. this._anchorBox = anchorBox;
  301. this._preferAlignAtTop = preferAlignAtTop;
  302. this._placeAtAnchor(this._anchorBox, (_a = this._userSize) !== null && _a !== void 0 ? _a : this.widget.size, preferAlignAtTop);
  303. }
  304. _placeAtAnchor(anchorBox, size, preferAlignAtTop) {
  305. var _a;
  306. const bodyBox = dom.getClientArea(document.body);
  307. const info = this.widget.getLayoutInfo();
  308. const defaultMinSize = new dom.Dimension(220, 2 * info.lineHeight);
  309. const defaultTop = anchorBox.top;
  310. // EAST
  311. const eastPlacement = (function () {
  312. const width = bodyBox.width - (anchorBox.left + anchorBox.width + info.borderWidth + info.horizontalPadding);
  313. const left = -info.borderWidth + anchorBox.left + anchorBox.width;
  314. const maxSizeTop = new dom.Dimension(width, bodyBox.height - anchorBox.top - info.borderHeight - info.verticalPadding);
  315. const maxSizeBottom = maxSizeTop.with(undefined, anchorBox.top + anchorBox.height - info.borderHeight - info.verticalPadding);
  316. return { top: defaultTop, left, fit: width - size.width, maxSizeTop, maxSizeBottom, minSize: defaultMinSize.with(Math.min(width, defaultMinSize.width)) };
  317. })();
  318. // WEST
  319. const westPlacement = (function () {
  320. const width = anchorBox.left - info.borderWidth - info.horizontalPadding;
  321. const left = Math.max(info.horizontalPadding, anchorBox.left - size.width - info.borderWidth);
  322. const maxSizeTop = new dom.Dimension(width, bodyBox.height - anchorBox.top - info.borderHeight - info.verticalPadding);
  323. const maxSizeBottom = maxSizeTop.with(undefined, anchorBox.top + anchorBox.height - info.borderHeight - info.verticalPadding);
  324. return { top: defaultTop, left, fit: width - size.width, maxSizeTop, maxSizeBottom, minSize: defaultMinSize.with(Math.min(width, defaultMinSize.width)) };
  325. })();
  326. // SOUTH
  327. const southPacement = (function () {
  328. const left = anchorBox.left;
  329. const top = -info.borderWidth + anchorBox.top + anchorBox.height;
  330. const maxSizeBottom = new dom.Dimension(anchorBox.width - info.borderHeight, bodyBox.height - anchorBox.top - anchorBox.height - info.verticalPadding);
  331. return { top, left, fit: maxSizeBottom.height - size.height, maxSizeBottom, maxSizeTop: maxSizeBottom, minSize: defaultMinSize.with(maxSizeBottom.width) };
  332. })();
  333. // take first placement that fits or the first with "least bad" fit
  334. const placements = [eastPlacement, westPlacement, southPacement];
  335. const placement = (_a = placements.find(p => p.fit >= 0)) !== null && _a !== void 0 ? _a : placements.sort((a, b) => b.fit - a.fit)[0];
  336. // top/bottom placement
  337. const bottom = anchorBox.top + anchorBox.height - info.borderHeight;
  338. let alignAtTop;
  339. let height = size.height;
  340. const maxHeight = Math.max(placement.maxSizeTop.height, placement.maxSizeBottom.height);
  341. if (height > maxHeight) {
  342. height = maxHeight;
  343. }
  344. let maxSize;
  345. if (preferAlignAtTop) {
  346. if (height <= placement.maxSizeTop.height) {
  347. alignAtTop = true;
  348. maxSize = placement.maxSizeTop;
  349. }
  350. else {
  351. alignAtTop = false;
  352. maxSize = placement.maxSizeBottom;
  353. }
  354. }
  355. else {
  356. if (height <= placement.maxSizeBottom.height) {
  357. alignAtTop = false;
  358. maxSize = placement.maxSizeBottom;
  359. }
  360. else {
  361. alignAtTop = true;
  362. maxSize = placement.maxSizeTop;
  363. }
  364. }
  365. this._applyTopLeft({ left: placement.left, top: alignAtTop ? placement.top : bottom - height });
  366. this.getDomNode().style.position = 'fixed';
  367. this._resizable.enableSashes(!alignAtTop, placement === eastPlacement, alignAtTop, placement !== eastPlacement);
  368. this._resizable.minSize = placement.minSize;
  369. this._resizable.maxSize = maxSize;
  370. this._resizable.layout(height, Math.min(maxSize.width, size.width));
  371. this.widget.layout(this._resizable.size.width, this._resizable.size.height);
  372. }
  373. _applyTopLeft(topLeft) {
  374. this._topLeft = topLeft;
  375. this.getDomNode().style.left = `${this._topLeft.left}px`;
  376. this.getDomNode().style.top = `${this._topLeft.top}px`;
  377. }
  378. }