24b15e0b073db3cfe004e93cfe551dd2dfbdce1ef9b91d2a93829349cf27066da4725b57c24bedbb261c496c70c094c83aa7e33dde1d6e0c2d3c60a851513f 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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 * as aria from '../../../../base/browser/ui/aria/aria.js';
  16. import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js';
  17. import { Codicon } from '../../../../base/common/codicons.js';
  18. import { Event } from '../../../../base/common/event.js';
  19. import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
  20. import { escapeRegExpCharacters } from '../../../../base/common/strings.js';
  21. import { assertIsDefined } from '../../../../base/common/types.js';
  22. import './parameterHints.css';
  23. import { ILanguageService } from '../../../common/languages/language.js';
  24. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  25. import { MarkdownRenderer } from '../../markdownRenderer/browser/markdownRenderer.js';
  26. import { ParameterHintsModel } from './parameterHintsModel.js';
  27. import { Context } from './provideSignatureHelp.js';
  28. import * as nls from '../../../../nls.js';
  29. import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
  30. import { IOpenerService } from '../../../../platform/opener/common/opener.js';
  31. import { editorHoverBackground, editorHoverBorder, editorHoverForeground, listHighlightForeground, registerColor, textCodeBlockBackground, textLinkActiveForeground, textLinkForeground } from '../../../../platform/theme/common/colorRegistry.js';
  32. import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
  33. import { isHighContrast } from '../../../../platform/theme/common/theme.js';
  34. import { registerThemingParticipant, ThemeIcon } from '../../../../platform/theme/common/themeService.js';
  35. const $ = dom.$;
  36. const parameterHintsNextIcon = registerIcon('parameter-hints-next', Codicon.chevronDown, nls.localize('parameterHintsNextIcon', 'Icon for show next parameter hint.'));
  37. const parameterHintsPreviousIcon = registerIcon('parameter-hints-previous', Codicon.chevronUp, nls.localize('parameterHintsPreviousIcon', 'Icon for show previous parameter hint.'));
  38. let ParameterHintsWidget = class ParameterHintsWidget extends Disposable {
  39. constructor(editor, contextKeyService, openerService, languageService, languageFeaturesService) {
  40. super();
  41. this.editor = editor;
  42. this.renderDisposeables = this._register(new DisposableStore());
  43. this.visible = false;
  44. this.announcedLabel = null;
  45. // Editor.IContentWidget.allowEditorOverflow
  46. this.allowEditorOverflow = true;
  47. this.markdownRenderer = this._register(new MarkdownRenderer({ editor }, languageService, openerService));
  48. this.model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider));
  49. this.keyVisible = Context.Visible.bindTo(contextKeyService);
  50. this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService);
  51. this._register(this.model.onChangedHints(newParameterHints => {
  52. if (newParameterHints) {
  53. this.show();
  54. this.render(newParameterHints);
  55. }
  56. else {
  57. this.hide();
  58. }
  59. }));
  60. }
  61. createParameterHintDOMNodes() {
  62. const element = $('.editor-widget.parameter-hints-widget');
  63. const wrapper = dom.append(element, $('.phwrapper'));
  64. wrapper.tabIndex = -1;
  65. const controls = dom.append(wrapper, $('.controls'));
  66. const previous = dom.append(controls, $('.button' + ThemeIcon.asCSSSelector(parameterHintsPreviousIcon)));
  67. const overloads = dom.append(controls, $('.overloads'));
  68. const next = dom.append(controls, $('.button' + ThemeIcon.asCSSSelector(parameterHintsNextIcon)));
  69. this._register(dom.addDisposableListener(previous, 'click', e => {
  70. dom.EventHelper.stop(e);
  71. this.previous();
  72. }));
  73. this._register(dom.addDisposableListener(next, 'click', e => {
  74. dom.EventHelper.stop(e);
  75. this.next();
  76. }));
  77. const body = $('.body');
  78. const scrollbar = new DomScrollableElement(body, {
  79. alwaysConsumeMouseWheel: true,
  80. });
  81. this._register(scrollbar);
  82. wrapper.appendChild(scrollbar.getDomNode());
  83. const signature = dom.append(body, $('.signature'));
  84. const docs = dom.append(body, $('.docs'));
  85. element.style.userSelect = 'text';
  86. this.domNodes = {
  87. element,
  88. signature,
  89. overloads,
  90. docs,
  91. scrollbar,
  92. };
  93. this.editor.addContentWidget(this);
  94. this.hide();
  95. this._register(this.editor.onDidChangeCursorSelection(e => {
  96. if (this.visible) {
  97. this.editor.layoutContentWidget(this);
  98. }
  99. }));
  100. const updateFont = () => {
  101. if (!this.domNodes) {
  102. return;
  103. }
  104. const fontInfo = this.editor.getOption(46 /* EditorOption.fontInfo */);
  105. this.domNodes.element.style.fontSize = `${fontInfo.fontSize}px`;
  106. this.domNodes.element.style.lineHeight = `${fontInfo.lineHeight / fontInfo.fontSize}`;
  107. };
  108. updateFont();
  109. this._register(Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor))
  110. .filter(e => e.hasChanged(46 /* EditorOption.fontInfo */))
  111. .on(updateFont, null));
  112. this._register(this.editor.onDidLayoutChange(e => this.updateMaxHeight()));
  113. this.updateMaxHeight();
  114. }
  115. show() {
  116. if (this.visible) {
  117. return;
  118. }
  119. if (!this.domNodes) {
  120. this.createParameterHintDOMNodes();
  121. }
  122. this.keyVisible.set(true);
  123. this.visible = true;
  124. setTimeout(() => {
  125. var _a;
  126. (_a = this.domNodes) === null || _a === void 0 ? void 0 : _a.element.classList.add('visible');
  127. }, 100);
  128. this.editor.layoutContentWidget(this);
  129. }
  130. hide() {
  131. var _a;
  132. this.renderDisposeables.clear();
  133. if (!this.visible) {
  134. return;
  135. }
  136. this.keyVisible.reset();
  137. this.visible = false;
  138. this.announcedLabel = null;
  139. (_a = this.domNodes) === null || _a === void 0 ? void 0 : _a.element.classList.remove('visible');
  140. this.editor.layoutContentWidget(this);
  141. }
  142. getPosition() {
  143. if (this.visible) {
  144. return {
  145. position: this.editor.getPosition(),
  146. preference: [1 /* ContentWidgetPositionPreference.ABOVE */, 2 /* ContentWidgetPositionPreference.BELOW */]
  147. };
  148. }
  149. return null;
  150. }
  151. render(hints) {
  152. var _a;
  153. this.renderDisposeables.clear();
  154. if (!this.domNodes) {
  155. return;
  156. }
  157. const multiple = hints.signatures.length > 1;
  158. this.domNodes.element.classList.toggle('multiple', multiple);
  159. this.keyMultipleSignatures.set(multiple);
  160. this.domNodes.signature.innerText = '';
  161. this.domNodes.docs.innerText = '';
  162. const signature = hints.signatures[hints.activeSignature];
  163. if (!signature) {
  164. return;
  165. }
  166. const code = dom.append(this.domNodes.signature, $('.code'));
  167. const fontInfo = this.editor.getOption(46 /* EditorOption.fontInfo */);
  168. code.style.fontSize = `${fontInfo.fontSize}px`;
  169. code.style.fontFamily = fontInfo.fontFamily;
  170. const hasParameters = signature.parameters.length > 0;
  171. const activeParameterIndex = (_a = signature.activeParameter) !== null && _a !== void 0 ? _a : hints.activeParameter;
  172. if (!hasParameters) {
  173. const label = dom.append(code, $('span'));
  174. label.textContent = signature.label;
  175. }
  176. else {
  177. this.renderParameters(code, signature, activeParameterIndex);
  178. }
  179. const activeParameter = signature.parameters[activeParameterIndex];
  180. if (activeParameter === null || activeParameter === void 0 ? void 0 : activeParameter.documentation) {
  181. const documentation = $('span.documentation');
  182. if (typeof activeParameter.documentation === 'string') {
  183. documentation.textContent = activeParameter.documentation;
  184. }
  185. else {
  186. const renderedContents = this.renderMarkdownDocs(activeParameter.documentation);
  187. documentation.appendChild(renderedContents.element);
  188. }
  189. dom.append(this.domNodes.docs, $('p', {}, documentation));
  190. }
  191. if (signature.documentation === undefined) {
  192. /** no op */
  193. }
  194. else if (typeof signature.documentation === 'string') {
  195. dom.append(this.domNodes.docs, $('p', {}, signature.documentation));
  196. }
  197. else {
  198. const renderedContents = this.renderMarkdownDocs(signature.documentation);
  199. dom.append(this.domNodes.docs, renderedContents.element);
  200. }
  201. const hasDocs = this.hasDocs(signature, activeParameter);
  202. this.domNodes.signature.classList.toggle('has-docs', hasDocs);
  203. this.domNodes.docs.classList.toggle('empty', !hasDocs);
  204. this.domNodes.overloads.textContent =
  205. String(hints.activeSignature + 1).padStart(hints.signatures.length.toString().length, '0') + '/' + hints.signatures.length;
  206. if (activeParameter) {
  207. let labelToAnnounce = '';
  208. const param = signature.parameters[activeParameterIndex];
  209. if (Array.isArray(param.label)) {
  210. labelToAnnounce = signature.label.substring(param.label[0], param.label[1]);
  211. }
  212. else {
  213. labelToAnnounce = param.label;
  214. }
  215. if (param.documentation) {
  216. labelToAnnounce += typeof param.documentation === 'string' ? `, ${param.documentation}` : `, ${param.documentation.value}`;
  217. }
  218. if (signature.documentation) {
  219. labelToAnnounce += typeof signature.documentation === 'string' ? `, ${signature.documentation}` : `, ${signature.documentation.value}`;
  220. }
  221. // Select method gets called on every user type while parameter hints are visible.
  222. // We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
  223. if (this.announcedLabel !== labelToAnnounce) {
  224. aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
  225. this.announcedLabel = labelToAnnounce;
  226. }
  227. }
  228. this.editor.layoutContentWidget(this);
  229. this.domNodes.scrollbar.scanDomNode();
  230. }
  231. renderMarkdownDocs(markdown) {
  232. const renderedContents = this.renderDisposeables.add(this.markdownRenderer.render(markdown, {
  233. asyncRenderCallback: () => {
  234. var _a;
  235. (_a = this.domNodes) === null || _a === void 0 ? void 0 : _a.scrollbar.scanDomNode();
  236. }
  237. }));
  238. renderedContents.element.classList.add('markdown-docs');
  239. return renderedContents;
  240. }
  241. hasDocs(signature, activeParameter) {
  242. if (activeParameter && typeof activeParameter.documentation === 'string' && assertIsDefined(activeParameter.documentation).length > 0) {
  243. return true;
  244. }
  245. if (activeParameter && typeof activeParameter.documentation === 'object' && assertIsDefined(activeParameter.documentation).value.length > 0) {
  246. return true;
  247. }
  248. if (signature.documentation && typeof signature.documentation === 'string' && assertIsDefined(signature.documentation).length > 0) {
  249. return true;
  250. }
  251. if (signature.documentation && typeof signature.documentation === 'object' && assertIsDefined(signature.documentation.value).length > 0) {
  252. return true;
  253. }
  254. return false;
  255. }
  256. renderParameters(parent, signature, activeParameterIndex) {
  257. const [start, end] = this.getParameterLabelOffsets(signature, activeParameterIndex);
  258. const beforeSpan = document.createElement('span');
  259. beforeSpan.textContent = signature.label.substring(0, start);
  260. const paramSpan = document.createElement('span');
  261. paramSpan.textContent = signature.label.substring(start, end);
  262. paramSpan.className = 'parameter active';
  263. const afterSpan = document.createElement('span');
  264. afterSpan.textContent = signature.label.substring(end);
  265. dom.append(parent, beforeSpan, paramSpan, afterSpan);
  266. }
  267. getParameterLabelOffsets(signature, paramIdx) {
  268. const param = signature.parameters[paramIdx];
  269. if (!param) {
  270. return [0, 0];
  271. }
  272. else if (Array.isArray(param.label)) {
  273. return param.label;
  274. }
  275. else if (!param.label.length) {
  276. return [0, 0];
  277. }
  278. else {
  279. const regex = new RegExp(`(\\W|^)${escapeRegExpCharacters(param.label)}(?=\\W|$)`, 'g');
  280. regex.test(signature.label);
  281. const idx = regex.lastIndex - param.label.length;
  282. return idx >= 0
  283. ? [idx, regex.lastIndex]
  284. : [0, 0];
  285. }
  286. }
  287. next() {
  288. this.editor.focus();
  289. this.model.next();
  290. }
  291. previous() {
  292. this.editor.focus();
  293. this.model.previous();
  294. }
  295. cancel() {
  296. this.model.cancel();
  297. }
  298. getDomNode() {
  299. if (!this.domNodes) {
  300. this.createParameterHintDOMNodes();
  301. }
  302. return this.domNodes.element;
  303. }
  304. getId() {
  305. return ParameterHintsWidget.ID;
  306. }
  307. trigger(context) {
  308. this.model.trigger(context, 0);
  309. }
  310. updateMaxHeight() {
  311. if (!this.domNodes) {
  312. return;
  313. }
  314. const height = Math.max(this.editor.getLayoutInfo().height / 4, 250);
  315. const maxHeight = `${height}px`;
  316. this.domNodes.element.style.maxHeight = maxHeight;
  317. const wrapper = this.domNodes.element.getElementsByClassName('phwrapper');
  318. if (wrapper.length) {
  319. wrapper[0].style.maxHeight = maxHeight;
  320. }
  321. }
  322. };
  323. ParameterHintsWidget.ID = 'editor.widget.parameterHintsWidget';
  324. ParameterHintsWidget = __decorate([
  325. __param(1, IContextKeyService),
  326. __param(2, IOpenerService),
  327. __param(3, ILanguageService),
  328. __param(4, ILanguageFeaturesService)
  329. ], ParameterHintsWidget);
  330. export { ParameterHintsWidget };
  331. export const editorHoverWidgetHighlightForeground = registerColor('editorHoverWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('editorHoverWidgetHighlightForeground', 'Foreground color of the active item in the parameter hint.'));
  332. registerThemingParticipant((theme, collector) => {
  333. const border = theme.getColor(editorHoverBorder);
  334. if (border) {
  335. const borderWidth = isHighContrast(theme.type) ? 2 : 1;
  336. collector.addRule(`.monaco-editor .parameter-hints-widget { border: ${borderWidth}px solid ${border}; }`);
  337. collector.addRule(`.monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid ${border.transparent(0.5)}; }`);
  338. collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`);
  339. }
  340. const background = theme.getColor(editorHoverBackground);
  341. if (background) {
  342. collector.addRule(`.monaco-editor .parameter-hints-widget { background-color: ${background}; }`);
  343. }
  344. const link = theme.getColor(textLinkForeground);
  345. if (link) {
  346. collector.addRule(`.monaco-editor .parameter-hints-widget a { color: ${link}; }`);
  347. }
  348. const linkHover = theme.getColor(textLinkActiveForeground);
  349. if (linkHover) {
  350. collector.addRule(`.monaco-editor .parameter-hints-widget a:hover { color: ${linkHover}; }`);
  351. }
  352. const foreground = theme.getColor(editorHoverForeground);
  353. if (foreground) {
  354. collector.addRule(`.monaco-editor .parameter-hints-widget { color: ${foreground}; }`);
  355. }
  356. const codeBackground = theme.getColor(textCodeBlockBackground);
  357. if (codeBackground) {
  358. collector.addRule(`.monaco-editor .parameter-hints-widget code { background-color: ${codeBackground}; }`);
  359. }
  360. const parameterHighlightColor = theme.getColor(editorHoverWidgetHighlightForeground);
  361. if (parameterHighlightColor) {
  362. collector.addRule(`.monaco-editor .parameter-hints-widget .parameter.active { color: ${parameterHighlightColor}}`);
  363. }
  364. });