741802ff11f00a8a7340693b41b7c643bc40ed1f1675538bd771f4728894960260ee37f3846894a04e16ea48568535606f4dfdbf777a8e23986eecd23a5662 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import * as dom from '../../../../base/browser/dom.js';
  24. import '../../../../base/browser/ui/codicons/codiconStyles.js'; // The codicon symbol styles are defined here and must be loaded
  25. import { List } from '../../../../base/browser/ui/list/listWidget.js';
  26. import { createCancelablePromise, disposableTimeout, TimeoutTimer } from '../../../../base/common/async.js';
  27. import { onUnexpectedError } from '../../../../base/common/errors.js';
  28. import { Emitter } from '../../../../base/common/event.js';
  29. import { DisposableStore } from '../../../../base/common/lifecycle.js';
  30. import { clamp } from '../../../../base/common/numbers.js';
  31. import * as strings from '../../../../base/common/strings.js';
  32. import './media/suggest.css';
  33. import { EmbeddedCodeEditorWidget } from '../../../browser/widget/embeddedCodeEditorWidget.js';
  34. import { SuggestWidgetStatus } from './suggestWidgetStatus.js';
  35. import '../../symbolIcons/browser/symbolIcons.js'; // The codicon symbol colors are defined here and must be loaded to get colors
  36. import * as nls from '../../../../nls.js';
  37. import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
  38. import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
  39. import { IStorageService } from '../../../../platform/storage/common/storage.js';
  40. import { activeContrastBorder, editorForeground, editorWidgetBackground, editorWidgetBorder, listFocusHighlightForeground, listHighlightForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js';
  41. import { attachListStyler } from '../../../../platform/theme/common/styler.js';
  42. import { isHighContrast } from '../../../../platform/theme/common/theme.js';
  43. import { IThemeService } from '../../../../platform/theme/common/themeService.js';
  44. import { ResizableHTMLElement } from '../../../../base/browser/ui/resizable/resizable.js';
  45. import { Context as SuggestContext } from './suggest.js';
  46. import { canExpandCompletionItem, SuggestDetailsOverlay, SuggestDetailsWidget } from './suggestWidgetDetails.js';
  47. import { getAriaId, ItemRenderer } from './suggestWidgetRenderer.js';
  48. /**
  49. * Suggest widget colors
  50. */
  51. export const editorSuggestWidgetBackground = registerColor('editorSuggestWidget.background', { dark: editorWidgetBackground, light: editorWidgetBackground, hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, nls.localize('editorSuggestWidgetBackground', 'Background color of the suggest widget.'));
  52. export const editorSuggestWidgetBorder = registerColor('editorSuggestWidget.border', { dark: editorWidgetBorder, light: editorWidgetBorder, hcDark: editorWidgetBorder, hcLight: editorWidgetBorder }, nls.localize('editorSuggestWidgetBorder', 'Border color of the suggest widget.'));
  53. export const editorSuggestWidgetForeground = registerColor('editorSuggestWidget.foreground', { dark: editorForeground, light: editorForeground, hcDark: editorForeground, hcLight: editorForeground }, nls.localize('editorSuggestWidgetForeground', 'Foreground color of the suggest widget.'));
  54. export const editorSuggestWidgetSelectedForeground = registerColor('editorSuggestWidget.selectedForeground', { dark: quickInputListFocusForeground, light: quickInputListFocusForeground, hcDark: quickInputListFocusForeground, hcLight: quickInputListFocusForeground }, nls.localize('editorSuggestWidgetSelectedForeground', 'Foreground color of the selected entry in the suggest widget.'));
  55. export const editorSuggestWidgetSelectedIconForeground = registerColor('editorSuggestWidget.selectedIconForeground', { dark: quickInputListFocusIconForeground, light: quickInputListFocusIconForeground, hcDark: quickInputListFocusIconForeground, hcLight: quickInputListFocusIconForeground }, nls.localize('editorSuggestWidgetSelectedIconForeground', 'Icon foreground color of the selected entry in the suggest widget.'));
  56. export const editorSuggestWidgetSelectedBackground = registerColor('editorSuggestWidget.selectedBackground', { dark: quickInputListFocusBackground, light: quickInputListFocusBackground, hcDark: quickInputListFocusBackground, hcLight: quickInputListFocusBackground }, nls.localize('editorSuggestWidgetSelectedBackground', 'Background color of the selected entry in the suggest widget.'));
  57. export const editorSuggestWidgetHighlightForeground = registerColor('editorSuggestWidget.highlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hcDark: listHighlightForeground, hcLight: listHighlightForeground }, nls.localize('editorSuggestWidgetHighlightForeground', 'Color of the match highlights in the suggest widget.'));
  58. export const editorSuggestWidgetHighlightFocusForeground = registerColor('editorSuggestWidget.focusHighlightForeground', { dark: listFocusHighlightForeground, light: listFocusHighlightForeground, hcDark: listFocusHighlightForeground, hcLight: listFocusHighlightForeground }, nls.localize('editorSuggestWidgetFocusHighlightForeground', 'Color of the match highlights in the suggest widget when an item is focused.'));
  59. export const editorSuggestWidgetStatusForeground = registerColor('editorSuggestWidgetStatus.foreground', { dark: transparent(editorSuggestWidgetForeground, .5), light: transparent(editorSuggestWidgetForeground, .5), hcDark: transparent(editorSuggestWidgetForeground, .5), hcLight: transparent(editorSuggestWidgetForeground, .5) }, nls.localize('editorSuggestWidgetStatusForeground', 'Foreground color of the suggest widget status.'));
  60. class PersistedWidgetSize {
  61. constructor(_service, editor) {
  62. this._service = _service;
  63. this._key = `suggestWidget.size/${editor.getEditorType()}/${editor instanceof EmbeddedCodeEditorWidget}`;
  64. }
  65. restore() {
  66. var _a;
  67. const raw = (_a = this._service.get(this._key, 0 /* StorageScope.PROFILE */)) !== null && _a !== void 0 ? _a : '';
  68. try {
  69. const obj = JSON.parse(raw);
  70. if (dom.Dimension.is(obj)) {
  71. return dom.Dimension.lift(obj);
  72. }
  73. }
  74. catch (_b) {
  75. // ignore
  76. }
  77. return undefined;
  78. }
  79. store(size) {
  80. this._service.store(this._key, JSON.stringify(size), 0 /* StorageScope.PROFILE */, 1 /* StorageTarget.MACHINE */);
  81. }
  82. reset() {
  83. this._service.remove(this._key, 0 /* StorageScope.PROFILE */);
  84. }
  85. }
  86. let SuggestWidget = class SuggestWidget {
  87. constructor(editor, _storageService, _contextKeyService, _themeService, instantiationService) {
  88. this.editor = editor;
  89. this._storageService = _storageService;
  90. this._state = 0 /* State.Hidden */;
  91. this._isAuto = false;
  92. this._ignoreFocusEvents = false;
  93. this._forceRenderingAbove = false;
  94. this._explainMode = false;
  95. this._showTimeout = new TimeoutTimer();
  96. this._disposables = new DisposableStore();
  97. this._onDidSelect = new Emitter();
  98. this._onDidFocus = new Emitter();
  99. this._onDidHide = new Emitter();
  100. this._onDidShow = new Emitter();
  101. this.onDidSelect = this._onDidSelect.event;
  102. this.onDidFocus = this._onDidFocus.event;
  103. this.onDidHide = this._onDidHide.event;
  104. this.onDidShow = this._onDidShow.event;
  105. this._onDetailsKeydown = new Emitter();
  106. this.onDetailsKeyDown = this._onDetailsKeydown.event;
  107. this.element = new ResizableHTMLElement();
  108. this.element.domNode.classList.add('editor-widget', 'suggest-widget');
  109. this._contentWidget = new SuggestContentWidget(this, editor);
  110. this._persistedSize = new PersistedWidgetSize(_storageService, editor);
  111. class ResizeState {
  112. constructor(persistedSize, currentSize, persistHeight = false, persistWidth = false) {
  113. this.persistedSize = persistedSize;
  114. this.currentSize = currentSize;
  115. this.persistHeight = persistHeight;
  116. this.persistWidth = persistWidth;
  117. }
  118. }
  119. let state;
  120. this._disposables.add(this.element.onDidWillResize(() => {
  121. this._contentWidget.lockPreference();
  122. state = new ResizeState(this._persistedSize.restore(), this.element.size);
  123. }));
  124. this._disposables.add(this.element.onDidResize(e => {
  125. var _a, _b, _c, _d;
  126. this._resize(e.dimension.width, e.dimension.height);
  127. if (state) {
  128. state.persistHeight = state.persistHeight || !!e.north || !!e.south;
  129. state.persistWidth = state.persistWidth || !!e.east || !!e.west;
  130. }
  131. if (!e.done) {
  132. return;
  133. }
  134. if (state) {
  135. // only store width or height value that have changed and also
  136. // only store changes that are above a certain threshold
  137. const { itemHeight, defaultSize } = this.getLayoutInfo();
  138. const threshold = Math.round(itemHeight / 2);
  139. let { width, height } = this.element.size;
  140. if (!state.persistHeight || Math.abs(state.currentSize.height - height) <= threshold) {
  141. height = (_b = (_a = state.persistedSize) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : defaultSize.height;
  142. }
  143. if (!state.persistWidth || Math.abs(state.currentSize.width - width) <= threshold) {
  144. width = (_d = (_c = state.persistedSize) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : defaultSize.width;
  145. }
  146. this._persistedSize.store(new dom.Dimension(width, height));
  147. }
  148. // reset working state
  149. this._contentWidget.unlockPreference();
  150. state = undefined;
  151. }));
  152. this._messageElement = dom.append(this.element.domNode, dom.$('.message'));
  153. this._listElement = dom.append(this.element.domNode, dom.$('.tree'));
  154. const details = instantiationService.createInstance(SuggestDetailsWidget, this.editor);
  155. details.onDidClose(this.toggleDetails, this, this._disposables);
  156. this._details = new SuggestDetailsOverlay(details, this.editor);
  157. const applyIconStyle = () => this.element.domNode.classList.toggle('no-icons', !this.editor.getOption(108 /* EditorOption.suggest */).showIcons);
  158. applyIconStyle();
  159. const renderer = instantiationService.createInstance(ItemRenderer, this.editor);
  160. this._disposables.add(renderer);
  161. this._disposables.add(renderer.onDidToggleDetails(() => this.toggleDetails()));
  162. this._list = new List('SuggestWidget', this._listElement, {
  163. getHeight: (_element) => this.getLayoutInfo().itemHeight,
  164. getTemplateId: (_element) => 'suggestion'
  165. }, [renderer], {
  166. alwaysConsumeMouseWheel: true,
  167. useShadows: false,
  168. mouseSupport: false,
  169. multipleSelectionSupport: false,
  170. accessibilityProvider: {
  171. getRole: () => 'option',
  172. getWidgetAriaLabel: () => nls.localize('suggest', "Suggest"),
  173. getWidgetRole: () => 'listbox',
  174. getAriaLabel: (item) => {
  175. let label = item.textLabel;
  176. if (typeof item.completion.label !== 'string') {
  177. const { detail, description } = item.completion.label;
  178. if (detail && description) {
  179. label = nls.localize('label.full', '{0}{1}, {2}', label, detail, description);
  180. }
  181. else if (detail) {
  182. label = nls.localize('label.detail', '{0}{1}', label, detail);
  183. }
  184. else if (description) {
  185. label = nls.localize('label.desc', '{0}, {1}', label, description);
  186. }
  187. }
  188. if (!item.isResolved || !this._isDetailsVisible()) {
  189. return label;
  190. }
  191. const { documentation, detail } = item.completion;
  192. const docs = strings.format('{0}{1}', detail || '', documentation ? (typeof documentation === 'string' ? documentation : documentation.value) : '');
  193. return nls.localize('ariaCurrenttSuggestionReadDetails', "{0}, docs: {1}", label, docs);
  194. },
  195. }
  196. });
  197. this._status = instantiationService.createInstance(SuggestWidgetStatus, this.element.domNode);
  198. const applyStatusBarStyle = () => this.element.domNode.classList.toggle('with-status-bar', this.editor.getOption(108 /* EditorOption.suggest */).showStatusBar);
  199. applyStatusBarStyle();
  200. this._disposables.add(attachListStyler(this._list, _themeService, {
  201. listInactiveFocusBackground: editorSuggestWidgetSelectedBackground,
  202. listInactiveFocusOutline: activeContrastBorder
  203. }));
  204. this._disposables.add(_themeService.onDidColorThemeChange(t => this._onThemeChange(t)));
  205. this._onThemeChange(_themeService.getColorTheme());
  206. this._disposables.add(this._list.onMouseDown(e => this._onListMouseDownOrTap(e)));
  207. this._disposables.add(this._list.onTap(e => this._onListMouseDownOrTap(e)));
  208. this._disposables.add(this._list.onDidChangeSelection(e => this._onListSelection(e)));
  209. this._disposables.add(this._list.onDidChangeFocus(e => this._onListFocus(e)));
  210. this._disposables.add(this.editor.onDidChangeCursorSelection(() => this._onCursorSelectionChanged()));
  211. this._disposables.add(this.editor.onDidChangeConfiguration(e => {
  212. if (e.hasChanged(108 /* EditorOption.suggest */)) {
  213. applyStatusBarStyle();
  214. applyIconStyle();
  215. }
  216. }));
  217. this._ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(_contextKeyService);
  218. this._ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(_contextKeyService);
  219. this._ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(_contextKeyService);
  220. this._ctxSuggestWidgetHasFocusedSuggestion = SuggestContext.HasFocusedSuggestion.bindTo(_contextKeyService);
  221. this._disposables.add(dom.addStandardDisposableListener(this._details.widget.domNode, 'keydown', e => {
  222. this._onDetailsKeydown.fire(e);
  223. }));
  224. this._disposables.add(this.editor.onMouseDown((e) => this._onEditorMouseDown(e)));
  225. }
  226. dispose() {
  227. var _a;
  228. this._details.widget.dispose();
  229. this._details.dispose();
  230. this._list.dispose();
  231. this._status.dispose();
  232. this._disposables.dispose();
  233. (_a = this._loadingTimeout) === null || _a === void 0 ? void 0 : _a.dispose();
  234. this._showTimeout.dispose();
  235. this._contentWidget.dispose();
  236. this.element.dispose();
  237. }
  238. _onEditorMouseDown(mouseEvent) {
  239. if (this._details.widget.domNode.contains(mouseEvent.target.element)) {
  240. // Clicking inside details
  241. this._details.widget.domNode.focus();
  242. }
  243. else {
  244. // Clicking outside details and inside suggest
  245. if (this.element.domNode.contains(mouseEvent.target.element)) {
  246. this.editor.focus();
  247. }
  248. }
  249. }
  250. _onCursorSelectionChanged() {
  251. if (this._state !== 0 /* State.Hidden */) {
  252. this._contentWidget.layout();
  253. }
  254. }
  255. _onListMouseDownOrTap(e) {
  256. if (typeof e.element === 'undefined' || typeof e.index === 'undefined') {
  257. return;
  258. }
  259. // prevent stealing browser focus from the editor
  260. e.browserEvent.preventDefault();
  261. e.browserEvent.stopPropagation();
  262. this._select(e.element, e.index);
  263. }
  264. _onListSelection(e) {
  265. if (e.elements.length) {
  266. this._select(e.elements[0], e.indexes[0]);
  267. }
  268. }
  269. _select(item, index) {
  270. const completionModel = this._completionModel;
  271. if (completionModel) {
  272. this._onDidSelect.fire({ item, index, model: completionModel });
  273. this.editor.focus();
  274. }
  275. }
  276. _onThemeChange(theme) {
  277. this._details.widget.borderWidth = isHighContrast(theme.type) ? 2 : 1;
  278. }
  279. _onListFocus(e) {
  280. var _a;
  281. if (this._ignoreFocusEvents) {
  282. return;
  283. }
  284. if (!e.elements.length) {
  285. if (this._currentSuggestionDetails) {
  286. this._currentSuggestionDetails.cancel();
  287. this._currentSuggestionDetails = undefined;
  288. this._focusedItem = undefined;
  289. }
  290. this.editor.setAriaOptions({ activeDescendant: undefined });
  291. this._ctxSuggestWidgetHasFocusedSuggestion.set(false);
  292. return;
  293. }
  294. if (!this._completionModel) {
  295. return;
  296. }
  297. this._ctxSuggestWidgetHasFocusedSuggestion.set(true);
  298. const item = e.elements[0];
  299. const index = e.indexes[0];
  300. if (item !== this._focusedItem) {
  301. (_a = this._currentSuggestionDetails) === null || _a === void 0 ? void 0 : _a.cancel();
  302. this._currentSuggestionDetails = undefined;
  303. this._focusedItem = item;
  304. this._list.reveal(index);
  305. this._currentSuggestionDetails = createCancelablePromise((token) => __awaiter(this, void 0, void 0, function* () {
  306. const loading = disposableTimeout(() => {
  307. if (this._isDetailsVisible()) {
  308. this.showDetails(true);
  309. }
  310. }, 250);
  311. const sub = token.onCancellationRequested(() => loading.dispose());
  312. const result = yield item.resolve(token);
  313. loading.dispose();
  314. sub.dispose();
  315. return result;
  316. }));
  317. this._currentSuggestionDetails.then(() => {
  318. if (index >= this._list.length || item !== this._list.element(index)) {
  319. return;
  320. }
  321. // item can have extra information, so re-render
  322. this._ignoreFocusEvents = true;
  323. this._list.splice(index, 1, [item]);
  324. this._list.setFocus([index]);
  325. this._ignoreFocusEvents = false;
  326. if (this._isDetailsVisible()) {
  327. this.showDetails(false);
  328. }
  329. else {
  330. this.element.domNode.classList.remove('docs-side');
  331. }
  332. this.editor.setAriaOptions({ activeDescendant: getAriaId(index) });
  333. }).catch(onUnexpectedError);
  334. }
  335. // emit an event
  336. this._onDidFocus.fire({ item, index, model: this._completionModel });
  337. }
  338. _setState(state) {
  339. if (this._state === state) {
  340. return;
  341. }
  342. this._state = state;
  343. this.element.domNode.classList.toggle('frozen', state === 4 /* State.Frozen */);
  344. this.element.domNode.classList.remove('message');
  345. switch (state) {
  346. case 0 /* State.Hidden */:
  347. dom.hide(this._messageElement, this._listElement, this._status.element);
  348. this._details.hide(true);
  349. this._status.hide();
  350. this._contentWidget.hide();
  351. this._ctxSuggestWidgetVisible.reset();
  352. this._ctxSuggestWidgetMultipleSuggestions.reset();
  353. this._ctxSuggestWidgetHasFocusedSuggestion.reset();
  354. this._showTimeout.cancel();
  355. this.element.domNode.classList.remove('visible');
  356. this._list.splice(0, this._list.length);
  357. this._focusedItem = undefined;
  358. this._cappedHeight = undefined;
  359. this._explainMode = false;
  360. break;
  361. case 1 /* State.Loading */:
  362. this.element.domNode.classList.add('message');
  363. this._messageElement.textContent = SuggestWidget.LOADING_MESSAGE;
  364. dom.hide(this._listElement, this._status.element);
  365. dom.show(this._messageElement);
  366. this._details.hide();
  367. this._show();
  368. this._focusedItem = undefined;
  369. break;
  370. case 2 /* State.Empty */:
  371. this.element.domNode.classList.add('message');
  372. this._messageElement.textContent = SuggestWidget.NO_SUGGESTIONS_MESSAGE;
  373. dom.hide(this._listElement, this._status.element);
  374. dom.show(this._messageElement);
  375. this._details.hide();
  376. this._show();
  377. this._focusedItem = undefined;
  378. break;
  379. case 3 /* State.Open */:
  380. dom.hide(this._messageElement);
  381. dom.show(this._listElement, this._status.element);
  382. this._show();
  383. break;
  384. case 4 /* State.Frozen */:
  385. dom.hide(this._messageElement);
  386. dom.show(this._listElement, this._status.element);
  387. this._show();
  388. break;
  389. case 5 /* State.Details */:
  390. dom.hide(this._messageElement);
  391. dom.show(this._listElement, this._status.element);
  392. this._details.show();
  393. this._show();
  394. break;
  395. }
  396. }
  397. _show() {
  398. this._status.show();
  399. this._contentWidget.show();
  400. this._layout(this._persistedSize.restore());
  401. this._ctxSuggestWidgetVisible.set(true);
  402. this._showTimeout.cancelAndSet(() => {
  403. this.element.domNode.classList.add('visible');
  404. this._onDidShow.fire(this);
  405. }, 100);
  406. }
  407. showTriggered(auto, delay) {
  408. if (this._state !== 0 /* State.Hidden */) {
  409. return;
  410. }
  411. this._contentWidget.setPosition(this.editor.getPosition());
  412. this._isAuto = !!auto;
  413. if (!this._isAuto) {
  414. this._loadingTimeout = disposableTimeout(() => this._setState(1 /* State.Loading */), delay);
  415. }
  416. }
  417. showSuggestions(completionModel, selectionIndex, isFrozen, isAuto) {
  418. var _a, _b;
  419. this._contentWidget.setPosition(this.editor.getPosition());
  420. (_a = this._loadingTimeout) === null || _a === void 0 ? void 0 : _a.dispose();
  421. (_b = this._currentSuggestionDetails) === null || _b === void 0 ? void 0 : _b.cancel();
  422. this._currentSuggestionDetails = undefined;
  423. if (this._completionModel !== completionModel) {
  424. this._completionModel = completionModel;
  425. }
  426. if (isFrozen && this._state !== 2 /* State.Empty */ && this._state !== 0 /* State.Hidden */) {
  427. this._setState(4 /* State.Frozen */);
  428. return;
  429. }
  430. const visibleCount = this._completionModel.items.length;
  431. const isEmpty = visibleCount === 0;
  432. this._ctxSuggestWidgetMultipleSuggestions.set(visibleCount > 1);
  433. if (isEmpty) {
  434. this._setState(isAuto ? 0 /* State.Hidden */ : 2 /* State.Empty */);
  435. this._completionModel = undefined;
  436. return;
  437. }
  438. this._focusedItem = undefined;
  439. this._list.splice(0, this._list.length, this._completionModel.items);
  440. this._setState(isFrozen ? 4 /* State.Frozen */ : 3 /* State.Open */);
  441. if (selectionIndex >= 0) {
  442. this._list.reveal(selectionIndex, 0);
  443. this._list.setFocus([selectionIndex]);
  444. }
  445. this._layout(this.element.size);
  446. // Reset focus border
  447. this._details.widget.domNode.classList.remove('focused');
  448. }
  449. selectNextPage() {
  450. switch (this._state) {
  451. case 0 /* State.Hidden */:
  452. return false;
  453. case 5 /* State.Details */:
  454. this._details.widget.pageDown();
  455. return true;
  456. case 1 /* State.Loading */:
  457. return !this._isAuto;
  458. default:
  459. this._list.focusNextPage();
  460. return true;
  461. }
  462. }
  463. selectNext() {
  464. switch (this._state) {
  465. case 0 /* State.Hidden */:
  466. return false;
  467. case 1 /* State.Loading */:
  468. return !this._isAuto;
  469. default:
  470. this._list.focusNext(1, true);
  471. return true;
  472. }
  473. }
  474. selectLast() {
  475. switch (this._state) {
  476. case 0 /* State.Hidden */:
  477. return false;
  478. case 5 /* State.Details */:
  479. this._details.widget.scrollBottom();
  480. return true;
  481. case 1 /* State.Loading */:
  482. return !this._isAuto;
  483. default:
  484. this._list.focusLast();
  485. return true;
  486. }
  487. }
  488. selectPreviousPage() {
  489. switch (this._state) {
  490. case 0 /* State.Hidden */:
  491. return false;
  492. case 5 /* State.Details */:
  493. this._details.widget.pageUp();
  494. return true;
  495. case 1 /* State.Loading */:
  496. return !this._isAuto;
  497. default:
  498. this._list.focusPreviousPage();
  499. return true;
  500. }
  501. }
  502. selectPrevious() {
  503. switch (this._state) {
  504. case 0 /* State.Hidden */:
  505. return false;
  506. case 1 /* State.Loading */:
  507. return !this._isAuto;
  508. default:
  509. this._list.focusPrevious(1, true);
  510. return false;
  511. }
  512. }
  513. selectFirst() {
  514. switch (this._state) {
  515. case 0 /* State.Hidden */:
  516. return false;
  517. case 5 /* State.Details */:
  518. this._details.widget.scrollTop();
  519. return true;
  520. case 1 /* State.Loading */:
  521. return !this._isAuto;
  522. default:
  523. this._list.focusFirst();
  524. return true;
  525. }
  526. }
  527. getFocusedItem() {
  528. if (this._state !== 0 /* State.Hidden */
  529. && this._state !== 2 /* State.Empty */
  530. && this._state !== 1 /* State.Loading */
  531. && this._completionModel) {
  532. return {
  533. item: this._list.getFocusedElements()[0],
  534. index: this._list.getFocus()[0],
  535. model: this._completionModel
  536. };
  537. }
  538. return undefined;
  539. }
  540. toggleDetailsFocus() {
  541. if (this._state === 5 /* State.Details */) {
  542. this._setState(3 /* State.Open */);
  543. this._details.widget.domNode.classList.remove('focused');
  544. }
  545. else if (this._state === 3 /* State.Open */ && this._isDetailsVisible()) {
  546. this._setState(5 /* State.Details */);
  547. this._details.widget.domNode.classList.add('focused');
  548. }
  549. }
  550. toggleDetails() {
  551. if (this._isDetailsVisible()) {
  552. // hide details widget
  553. this._ctxSuggestWidgetDetailsVisible.set(false);
  554. this._setDetailsVisible(false);
  555. this._details.hide();
  556. this.element.domNode.classList.remove('shows-details');
  557. }
  558. else if ((canExpandCompletionItem(this._list.getFocusedElements()[0]) || this._explainMode) && (this._state === 3 /* State.Open */ || this._state === 5 /* State.Details */ || this._state === 4 /* State.Frozen */)) {
  559. // show details widget (iff possible)
  560. this._ctxSuggestWidgetDetailsVisible.set(true);
  561. this._setDetailsVisible(true);
  562. this.showDetails(false);
  563. }
  564. }
  565. showDetails(loading) {
  566. this._details.show();
  567. if (loading) {
  568. this._details.widget.renderLoading();
  569. }
  570. else {
  571. this._details.widget.renderItem(this._list.getFocusedElements()[0], this._explainMode);
  572. }
  573. this._positionDetails();
  574. this.editor.focus();
  575. this.element.domNode.classList.add('shows-details');
  576. }
  577. toggleExplainMode() {
  578. if (this._list.getFocusedElements()[0]) {
  579. this._explainMode = !this._explainMode;
  580. if (!this._isDetailsVisible()) {
  581. this.toggleDetails();
  582. }
  583. else {
  584. this.showDetails(false);
  585. }
  586. }
  587. }
  588. resetPersistedSize() {
  589. this._persistedSize.reset();
  590. }
  591. hideWidget() {
  592. var _a;
  593. (_a = this._loadingTimeout) === null || _a === void 0 ? void 0 : _a.dispose();
  594. this._setState(0 /* State.Hidden */);
  595. this._onDidHide.fire(this);
  596. this.element.clearSashHoverState();
  597. // ensure that a reasonable widget height is persisted so that
  598. // accidential "resize-to-single-items" cases aren't happening
  599. const dim = this._persistedSize.restore();
  600. const minPersistedHeight = Math.ceil(this.getLayoutInfo().itemHeight * 4.3);
  601. if (dim && dim.height < minPersistedHeight) {
  602. this._persistedSize.store(dim.with(undefined, minPersistedHeight));
  603. }
  604. }
  605. isFrozen() {
  606. return this._state === 4 /* State.Frozen */;
  607. }
  608. _afterRender(position) {
  609. if (position === null) {
  610. if (this._isDetailsVisible()) {
  611. this._details.hide(); //todo@jrieken soft-hide
  612. }
  613. return;
  614. }
  615. if (this._state === 2 /* State.Empty */ || this._state === 1 /* State.Loading */) {
  616. // no special positioning when widget isn't showing list
  617. return;
  618. }
  619. if (this._isDetailsVisible()) {
  620. this._details.show();
  621. }
  622. this._positionDetails();
  623. }
  624. _layout(size) {
  625. var _a, _b, _c;
  626. if (!this.editor.hasModel()) {
  627. return;
  628. }
  629. if (!this.editor.getDomNode()) {
  630. // happens when running tests
  631. return;
  632. }
  633. const bodyBox = dom.getClientArea(document.body);
  634. const info = this.getLayoutInfo();
  635. if (!size) {
  636. size = info.defaultSize;
  637. }
  638. let height = size.height;
  639. let width = size.width;
  640. // status bar
  641. this._status.element.style.lineHeight = `${info.itemHeight}px`;
  642. if (this._state === 2 /* State.Empty */ || this._state === 1 /* State.Loading */) {
  643. // showing a message only
  644. height = info.itemHeight + info.borderHeight;
  645. width = info.defaultSize.width / 2;
  646. this.element.enableSashes(false, false, false, false);
  647. this.element.minSize = this.element.maxSize = new dom.Dimension(width, height);
  648. this._contentWidget.setPreference(2 /* ContentWidgetPositionPreference.BELOW */);
  649. }
  650. else {
  651. // showing items
  652. // width math
  653. const maxWidth = bodyBox.width - info.borderHeight - 2 * info.horizontalPadding;
  654. if (width > maxWidth) {
  655. width = maxWidth;
  656. }
  657. const preferredWidth = this._completionModel ? this._completionModel.stats.pLabelLen * info.typicalHalfwidthCharacterWidth : width;
  658. // height math
  659. const fullHeight = info.statusBarHeight + this._list.contentHeight + info.borderHeight;
  660. const minHeight = info.itemHeight + info.statusBarHeight;
  661. const editorBox = dom.getDomNodePagePosition(this.editor.getDomNode());
  662. const cursorBox = this.editor.getScrolledVisiblePosition(this.editor.getPosition());
  663. const cursorBottom = editorBox.top + cursorBox.top + cursorBox.height;
  664. const maxHeightBelow = Math.min(bodyBox.height - cursorBottom - info.verticalPadding, fullHeight);
  665. const availableSpaceAbove = editorBox.top + cursorBox.top - info.verticalPadding;
  666. const maxHeightAbove = Math.min(availableSpaceAbove, fullHeight);
  667. let maxHeight = Math.min(Math.max(maxHeightAbove, maxHeightBelow) + info.borderHeight, fullHeight);
  668. if (height === ((_a = this._cappedHeight) === null || _a === void 0 ? void 0 : _a.capped)) {
  669. // Restore the old (wanted) height when the current
  670. // height is capped to fit
  671. height = this._cappedHeight.wanted;
  672. }
  673. if (height < minHeight) {
  674. height = minHeight;
  675. }
  676. if (height > maxHeight) {
  677. height = maxHeight;
  678. }
  679. const forceRenderingAboveRequiredSpace = 150;
  680. if (height > maxHeightBelow || (this._forceRenderingAbove && availableSpaceAbove > forceRenderingAboveRequiredSpace)) {
  681. this._contentWidget.setPreference(1 /* ContentWidgetPositionPreference.ABOVE */);
  682. this.element.enableSashes(true, true, false, false);
  683. maxHeight = maxHeightAbove;
  684. }
  685. else {
  686. this._contentWidget.setPreference(2 /* ContentWidgetPositionPreference.BELOW */);
  687. this.element.enableSashes(false, true, true, false);
  688. maxHeight = maxHeightBelow;
  689. }
  690. this.element.preferredSize = new dom.Dimension(preferredWidth, info.defaultSize.height);
  691. this.element.maxSize = new dom.Dimension(maxWidth, maxHeight);
  692. this.element.minSize = new dom.Dimension(220, minHeight);
  693. // Know when the height was capped to fit and remember
  694. // the wanted height for later. This is required when going
  695. // left to widen suggestions.
  696. this._cappedHeight = height === fullHeight
  697. ? { wanted: (_c = (_b = this._cappedHeight) === null || _b === void 0 ? void 0 : _b.wanted) !== null && _c !== void 0 ? _c : size.height, capped: height }
  698. : undefined;
  699. }
  700. this._resize(width, height);
  701. }
  702. _resize(width, height) {
  703. const { width: maxWidth, height: maxHeight } = this.element.maxSize;
  704. width = Math.min(maxWidth, width);
  705. height = Math.min(maxHeight, height);
  706. const { statusBarHeight } = this.getLayoutInfo();
  707. this._list.layout(height - statusBarHeight, width);
  708. this._listElement.style.height = `${height - statusBarHeight}px`;
  709. this.element.layout(height, width);
  710. this._contentWidget.layout();
  711. this._positionDetails();
  712. }
  713. _positionDetails() {
  714. var _a;
  715. if (this._isDetailsVisible()) {
  716. this._details.placeAtAnchor(this.element.domNode, ((_a = this._contentWidget.getPosition()) === null || _a === void 0 ? void 0 : _a.preference[0]) === 2 /* ContentWidgetPositionPreference.BELOW */);
  717. }
  718. }
  719. getLayoutInfo() {
  720. const fontInfo = this.editor.getOption(46 /* EditorOption.fontInfo */);
  721. const itemHeight = clamp(this.editor.getOption(110 /* EditorOption.suggestLineHeight */) || fontInfo.lineHeight, 8, 1000);
  722. const statusBarHeight = !this.editor.getOption(108 /* EditorOption.suggest */).showStatusBar || this._state === 2 /* State.Empty */ || this._state === 1 /* State.Loading */ ? 0 : itemHeight;
  723. const borderWidth = this._details.widget.borderWidth;
  724. const borderHeight = 2 * borderWidth;
  725. return {
  726. itemHeight,
  727. statusBarHeight,
  728. borderWidth,
  729. borderHeight,
  730. typicalHalfwidthCharacterWidth: fontInfo.typicalHalfwidthCharacterWidth,
  731. verticalPadding: 22,
  732. horizontalPadding: 14,
  733. defaultSize: new dom.Dimension(430, statusBarHeight + 12 * itemHeight + borderHeight)
  734. };
  735. }
  736. _isDetailsVisible() {
  737. return this._storageService.getBoolean('expandSuggestionDocs', 0 /* StorageScope.PROFILE */, false);
  738. }
  739. _setDetailsVisible(value) {
  740. this._storageService.store('expandSuggestionDocs', value, 0 /* StorageScope.PROFILE */, 0 /* StorageTarget.USER */);
  741. }
  742. forceRenderingAbove() {
  743. if (!this._forceRenderingAbove) {
  744. this._forceRenderingAbove = true;
  745. this._layout(this._persistedSize.restore());
  746. }
  747. }
  748. stopForceRenderingAbove() {
  749. this._forceRenderingAbove = false;
  750. }
  751. };
  752. SuggestWidget.LOADING_MESSAGE = nls.localize('suggestWidget.loading', "Loading...");
  753. SuggestWidget.NO_SUGGESTIONS_MESSAGE = nls.localize('suggestWidget.noSuggestions', "No suggestions.");
  754. SuggestWidget = __decorate([
  755. __param(1, IStorageService),
  756. __param(2, IContextKeyService),
  757. __param(3, IThemeService),
  758. __param(4, IInstantiationService)
  759. ], SuggestWidget);
  760. export { SuggestWidget };
  761. export class SuggestContentWidget {
  762. constructor(_widget, _editor) {
  763. this._widget = _widget;
  764. this._editor = _editor;
  765. this.allowEditorOverflow = true;
  766. this.suppressMouseDown = false;
  767. this._preferenceLocked = false;
  768. this._added = false;
  769. this._hidden = false;
  770. }
  771. dispose() {
  772. if (this._added) {
  773. this._added = false;
  774. this._editor.removeContentWidget(this);
  775. }
  776. }
  777. getId() {
  778. return 'editor.widget.suggestWidget';
  779. }
  780. getDomNode() {
  781. return this._widget.element.domNode;
  782. }
  783. show() {
  784. this._hidden = false;
  785. if (!this._added) {
  786. this._added = true;
  787. this._editor.addContentWidget(this);
  788. }
  789. }
  790. hide() {
  791. if (!this._hidden) {
  792. this._hidden = true;
  793. this.layout();
  794. }
  795. }
  796. layout() {
  797. this._editor.layoutContentWidget(this);
  798. }
  799. getPosition() {
  800. if (this._hidden || !this._position || !this._preference) {
  801. return null;
  802. }
  803. return {
  804. position: this._position,
  805. preference: [this._preference]
  806. };
  807. }
  808. beforeRender() {
  809. const { height, width } = this._widget.element.size;
  810. const { borderWidth, horizontalPadding } = this._widget.getLayoutInfo();
  811. return new dom.Dimension(width + 2 * borderWidth + horizontalPadding, height + 2 * borderWidth);
  812. }
  813. afterRender(position) {
  814. this._widget._afterRender(position);
  815. }
  816. setPreference(preference) {
  817. if (!this._preferenceLocked) {
  818. this._preference = preference;
  819. }
  820. }
  821. lockPreference() {
  822. this._preferenceLocked = true;
  823. }
  824. unlockPreference() {
  825. this._preferenceLocked = false;
  826. }
  827. setPosition(position) {
  828. this._position = position;
  829. }
  830. }