quickInput.js 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  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 '../../../browser/dom.js';
  15. import { StandardKeyboardEvent } from '../../../browser/keyboardEvent.js';
  16. import { ActionBar } from '../../../browser/ui/actionbar/actionbar.js';
  17. import { Button } from '../../../browser/ui/button/button.js';
  18. import { CountBadge } from '../../../browser/ui/countBadge/countBadge.js';
  19. import { renderLabelWithIcons } from '../../../browser/ui/iconLabel/iconLabels.js';
  20. import { ProgressBar } from '../../../browser/ui/progressbar/progressbar.js';
  21. import { Action } from '../../../common/actions.js';
  22. import { equals } from '../../../common/arrays.js';
  23. import { TimeoutTimer } from '../../../common/async.js';
  24. import { CancellationToken } from '../../../common/cancellation.js';
  25. import { Codicon } from '../../../common/codicons.js';
  26. import { Emitter } from '../../../common/event.js';
  27. import { Disposable, DisposableStore, dispose } from '../../../common/lifecycle.js';
  28. import { isIOS } from '../../../common/platform.js';
  29. import Severity from '../../../common/severity.js';
  30. import { withNullAsUndefined } from '../../../common/types.js';
  31. import { getIconClass } from './quickInputUtils.js';
  32. import { ItemActivation, NO_KEY_MODS, QuickInputHideReason } from '../common/quickInput.js';
  33. import './media/quickInput.css';
  34. import { localize } from '../../../../nls.js';
  35. import { QuickInputBox } from './quickInputBox.js';
  36. import { QuickInputList, QuickInputListFocus } from './quickInputList.js';
  37. const $ = dom.$;
  38. const backButton = {
  39. iconClass: Codicon.quickInputBack.classNames,
  40. tooltip: localize('quickInput.back', "Back"),
  41. handle: -1 // TODO
  42. };
  43. class QuickInput extends Disposable {
  44. constructor(ui) {
  45. super();
  46. this.ui = ui;
  47. this.visible = false;
  48. this._enabled = true;
  49. this._busy = false;
  50. this._ignoreFocusOut = false;
  51. this._buttons = [];
  52. this.noValidationMessage = QuickInput.noPromptMessage;
  53. this._severity = Severity.Ignore;
  54. this.buttonsUpdated = false;
  55. this.onDidTriggerButtonEmitter = this._register(new Emitter());
  56. this.onDidHideEmitter = this._register(new Emitter());
  57. this.onDisposeEmitter = this._register(new Emitter());
  58. this.visibleDisposables = this._register(new DisposableStore());
  59. this.onDidHide = this.onDidHideEmitter.event;
  60. }
  61. get title() {
  62. return this._title;
  63. }
  64. set title(title) {
  65. this._title = title;
  66. this.update();
  67. }
  68. get description() {
  69. return this._description;
  70. }
  71. set description(description) {
  72. this._description = description;
  73. this.update();
  74. }
  75. get step() {
  76. return this._steps;
  77. }
  78. set step(step) {
  79. this._steps = step;
  80. this.update();
  81. }
  82. get totalSteps() {
  83. return this._totalSteps;
  84. }
  85. set totalSteps(totalSteps) {
  86. this._totalSteps = totalSteps;
  87. this.update();
  88. }
  89. get enabled() {
  90. return this._enabled;
  91. }
  92. set enabled(enabled) {
  93. this._enabled = enabled;
  94. this.update();
  95. }
  96. get contextKey() {
  97. return this._contextKey;
  98. }
  99. set contextKey(contextKey) {
  100. this._contextKey = contextKey;
  101. this.update();
  102. }
  103. get busy() {
  104. return this._busy;
  105. }
  106. set busy(busy) {
  107. this._busy = busy;
  108. this.update();
  109. }
  110. get ignoreFocusOut() {
  111. return this._ignoreFocusOut;
  112. }
  113. set ignoreFocusOut(ignoreFocusOut) {
  114. const shouldUpdate = this._ignoreFocusOut !== ignoreFocusOut && !isIOS;
  115. this._ignoreFocusOut = ignoreFocusOut && !isIOS;
  116. if (shouldUpdate) {
  117. this.update();
  118. }
  119. }
  120. get buttons() {
  121. return this._buttons;
  122. }
  123. set buttons(buttons) {
  124. this._buttons = buttons;
  125. this.buttonsUpdated = true;
  126. this.update();
  127. }
  128. get validationMessage() {
  129. return this._validationMessage;
  130. }
  131. set validationMessage(validationMessage) {
  132. this._validationMessage = validationMessage;
  133. this.update();
  134. }
  135. get severity() {
  136. return this._severity;
  137. }
  138. set severity(severity) {
  139. this._severity = severity;
  140. this.update();
  141. }
  142. show() {
  143. if (this.visible) {
  144. return;
  145. }
  146. this.visibleDisposables.add(this.ui.onDidTriggerButton(button => {
  147. if (this.buttons.indexOf(button) !== -1) {
  148. this.onDidTriggerButtonEmitter.fire(button);
  149. }
  150. }));
  151. this.ui.show(this);
  152. // update properties in the controller that get reset in the ui.show() call
  153. this.visible = true;
  154. // This ensures the message/prompt gets rendered
  155. this._lastValidationMessage = undefined;
  156. // This ensures the input box has the right severity applied
  157. this._lastSeverity = undefined;
  158. if (this.buttons.length) {
  159. // if there are buttons, the ui.show() clears them out of the UI so we should
  160. // rerender them.
  161. this.buttonsUpdated = true;
  162. }
  163. this.update();
  164. }
  165. hide() {
  166. if (!this.visible) {
  167. return;
  168. }
  169. this.ui.hide();
  170. }
  171. didHide(reason = QuickInputHideReason.Other) {
  172. this.visible = false;
  173. this.visibleDisposables.clear();
  174. this.onDidHideEmitter.fire({ reason });
  175. }
  176. update() {
  177. if (!this.visible) {
  178. return;
  179. }
  180. const title = this.getTitle();
  181. if (title && this.ui.title.textContent !== title) {
  182. this.ui.title.textContent = title;
  183. }
  184. else if (!title && this.ui.title.innerHTML !== ' ') {
  185. this.ui.title.innerText = '\u00a0';
  186. }
  187. const description = this.getDescription();
  188. if (this.ui.description1.textContent !== description) {
  189. this.ui.description1.textContent = description;
  190. }
  191. if (this.ui.description2.textContent !== description) {
  192. this.ui.description2.textContent = description;
  193. }
  194. if (this.busy && !this.busyDelay) {
  195. this.busyDelay = new TimeoutTimer();
  196. this.busyDelay.setIfNotSet(() => {
  197. if (this.visible) {
  198. this.ui.progressBar.infinite();
  199. }
  200. }, 800);
  201. }
  202. if (!this.busy && this.busyDelay) {
  203. this.ui.progressBar.stop();
  204. this.busyDelay.cancel();
  205. this.busyDelay = undefined;
  206. }
  207. if (this.buttonsUpdated) {
  208. this.buttonsUpdated = false;
  209. this.ui.leftActionBar.clear();
  210. const leftButtons = this.buttons.filter(button => button === backButton);
  211. this.ui.leftActionBar.push(leftButtons.map((button, index) => {
  212. const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => __awaiter(this, void 0, void 0, function* () {
  213. this.onDidTriggerButtonEmitter.fire(button);
  214. }));
  215. action.tooltip = button.tooltip || '';
  216. return action;
  217. }), { icon: true, label: false });
  218. this.ui.rightActionBar.clear();
  219. const rightButtons = this.buttons.filter(button => button !== backButton);
  220. this.ui.rightActionBar.push(rightButtons.map((button, index) => {
  221. const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => __awaiter(this, void 0, void 0, function* () {
  222. this.onDidTriggerButtonEmitter.fire(button);
  223. }));
  224. action.tooltip = button.tooltip || '';
  225. return action;
  226. }), { icon: true, label: false });
  227. }
  228. this.ui.ignoreFocusOut = this.ignoreFocusOut;
  229. this.ui.setEnabled(this.enabled);
  230. this.ui.setContextKey(this.contextKey);
  231. const validationMessage = this.validationMessage || this.noValidationMessage;
  232. if (this._lastValidationMessage !== validationMessage) {
  233. this._lastValidationMessage = validationMessage;
  234. dom.reset(this.ui.message, ...renderLabelWithIcons(validationMessage));
  235. }
  236. if (this._lastSeverity !== this.severity) {
  237. this._lastSeverity = this.severity;
  238. this.showMessageDecoration(this.severity);
  239. }
  240. }
  241. getTitle() {
  242. if (this.title && this.step) {
  243. return `${this.title} (${this.getSteps()})`;
  244. }
  245. if (this.title) {
  246. return this.title;
  247. }
  248. if (this.step) {
  249. return this.getSteps();
  250. }
  251. return '';
  252. }
  253. getDescription() {
  254. return this.description || '';
  255. }
  256. getSteps() {
  257. if (this.step && this.totalSteps) {
  258. return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps);
  259. }
  260. if (this.step) {
  261. return String(this.step);
  262. }
  263. return '';
  264. }
  265. showMessageDecoration(severity) {
  266. this.ui.inputBox.showDecoration(severity);
  267. if (severity !== Severity.Ignore) {
  268. const styles = this.ui.inputBox.stylesForType(severity);
  269. this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : '';
  270. this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : '';
  271. this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : '';
  272. this.ui.message.style.paddingBottom = '4px';
  273. }
  274. else {
  275. this.ui.message.style.color = '';
  276. this.ui.message.style.backgroundColor = '';
  277. this.ui.message.style.border = '';
  278. this.ui.message.style.paddingBottom = '';
  279. }
  280. }
  281. dispose() {
  282. this.hide();
  283. this.onDisposeEmitter.fire();
  284. super.dispose();
  285. }
  286. }
  287. QuickInput.noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
  288. class QuickPick extends QuickInput {
  289. constructor() {
  290. super(...arguments);
  291. this._value = '';
  292. this.onDidChangeValueEmitter = this._register(new Emitter());
  293. this.onWillAcceptEmitter = this._register(new Emitter());
  294. this.onDidAcceptEmitter = this._register(new Emitter());
  295. this.onDidCustomEmitter = this._register(new Emitter());
  296. this._items = [];
  297. this.itemsUpdated = false;
  298. this._canSelectMany = false;
  299. this._canAcceptInBackground = false;
  300. this._matchOnDescription = false;
  301. this._matchOnDetail = false;
  302. this._matchOnLabel = true;
  303. this._sortByLabel = true;
  304. this._autoFocusOnList = true;
  305. this._keepScrollPosition = false;
  306. this._itemActivation = this.ui.isScreenReaderOptimized() ? ItemActivation.NONE /* https://github.com/microsoft/vscode/issues/57501 */ : ItemActivation.FIRST;
  307. this._activeItems = [];
  308. this.activeItemsUpdated = false;
  309. this.activeItemsToConfirm = [];
  310. this.onDidChangeActiveEmitter = this._register(new Emitter());
  311. this._selectedItems = [];
  312. this.selectedItemsUpdated = false;
  313. this.selectedItemsToConfirm = [];
  314. this.onDidChangeSelectionEmitter = this._register(new Emitter());
  315. this.onDidTriggerItemButtonEmitter = this._register(new Emitter());
  316. this.valueSelectionUpdated = true;
  317. this._ok = 'default';
  318. this._customButton = false;
  319. this.filterValue = (value) => value;
  320. this.onDidChangeValue = this.onDidChangeValueEmitter.event;
  321. this.onWillAccept = this.onWillAcceptEmitter.event;
  322. this.onDidAccept = this.onDidAcceptEmitter.event;
  323. this.onDidChangeActive = this.onDidChangeActiveEmitter.event;
  324. this.onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
  325. this.onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
  326. }
  327. get quickNavigate() {
  328. return this._quickNavigate;
  329. }
  330. set quickNavigate(quickNavigate) {
  331. this._quickNavigate = quickNavigate;
  332. this.update();
  333. }
  334. get value() {
  335. return this._value;
  336. }
  337. set value(value) {
  338. if (this._value !== value) {
  339. this._value = value || '';
  340. this.update();
  341. this.onDidChangeValueEmitter.fire(this._value);
  342. }
  343. }
  344. set ariaLabel(ariaLabel) {
  345. this._ariaLabel = ariaLabel;
  346. this.update();
  347. }
  348. get ariaLabel() {
  349. return this._ariaLabel;
  350. }
  351. get placeholder() {
  352. return this._placeholder;
  353. }
  354. set placeholder(placeholder) {
  355. this._placeholder = placeholder;
  356. this.update();
  357. }
  358. get items() {
  359. return this._items;
  360. }
  361. get scrollTop() {
  362. return this.ui.list.scrollTop;
  363. }
  364. set scrollTop(scrollTop) {
  365. this.ui.list.scrollTop = scrollTop;
  366. }
  367. set items(items) {
  368. this._items = items;
  369. this.itemsUpdated = true;
  370. this.update();
  371. }
  372. get canSelectMany() {
  373. return this._canSelectMany;
  374. }
  375. set canSelectMany(canSelectMany) {
  376. this._canSelectMany = canSelectMany;
  377. this.update();
  378. }
  379. get canAcceptInBackground() {
  380. return this._canAcceptInBackground;
  381. }
  382. set canAcceptInBackground(canAcceptInBackground) {
  383. this._canAcceptInBackground = canAcceptInBackground;
  384. }
  385. get matchOnDescription() {
  386. return this._matchOnDescription;
  387. }
  388. set matchOnDescription(matchOnDescription) {
  389. this._matchOnDescription = matchOnDescription;
  390. this.update();
  391. }
  392. get matchOnDetail() {
  393. return this._matchOnDetail;
  394. }
  395. set matchOnDetail(matchOnDetail) {
  396. this._matchOnDetail = matchOnDetail;
  397. this.update();
  398. }
  399. get matchOnLabel() {
  400. return this._matchOnLabel;
  401. }
  402. set matchOnLabel(matchOnLabel) {
  403. this._matchOnLabel = matchOnLabel;
  404. this.update();
  405. }
  406. get sortByLabel() {
  407. return this._sortByLabel;
  408. }
  409. set sortByLabel(sortByLabel) {
  410. this._sortByLabel = sortByLabel;
  411. this.update();
  412. }
  413. get autoFocusOnList() {
  414. return this._autoFocusOnList;
  415. }
  416. set autoFocusOnList(autoFocusOnList) {
  417. this._autoFocusOnList = autoFocusOnList;
  418. this.update();
  419. }
  420. get keepScrollPosition() {
  421. return this._keepScrollPosition;
  422. }
  423. set keepScrollPosition(keepScrollPosition) {
  424. this._keepScrollPosition = keepScrollPosition;
  425. }
  426. get itemActivation() {
  427. return this._itemActivation;
  428. }
  429. set itemActivation(itemActivation) {
  430. this._itemActivation = itemActivation;
  431. }
  432. get activeItems() {
  433. return this._activeItems;
  434. }
  435. set activeItems(activeItems) {
  436. this._activeItems = activeItems;
  437. this.activeItemsUpdated = true;
  438. this.update();
  439. }
  440. get selectedItems() {
  441. return this._selectedItems;
  442. }
  443. set selectedItems(selectedItems) {
  444. this._selectedItems = selectedItems;
  445. this.selectedItemsUpdated = true;
  446. this.update();
  447. }
  448. get keyMods() {
  449. if (this._quickNavigate) {
  450. // Disable keyMods when quick navigate is enabled
  451. // because in this model the interaction is purely
  452. // keyboard driven and Ctrl/Alt are typically
  453. // pressed and hold during this interaction.
  454. return NO_KEY_MODS;
  455. }
  456. return this.ui.keyMods;
  457. }
  458. set valueSelection(valueSelection) {
  459. this._valueSelection = valueSelection;
  460. this.valueSelectionUpdated = true;
  461. this.update();
  462. }
  463. get customButton() {
  464. return this._customButton;
  465. }
  466. set customButton(showCustomButton) {
  467. this._customButton = showCustomButton;
  468. this.update();
  469. }
  470. get customLabel() {
  471. return this._customButtonLabel;
  472. }
  473. set customLabel(label) {
  474. this._customButtonLabel = label;
  475. this.update();
  476. }
  477. get customHover() {
  478. return this._customButtonHover;
  479. }
  480. set customHover(hover) {
  481. this._customButtonHover = hover;
  482. this.update();
  483. }
  484. get ok() {
  485. return this._ok;
  486. }
  487. set ok(showOkButton) {
  488. this._ok = showOkButton;
  489. this.update();
  490. }
  491. get hideInput() {
  492. return !!this._hideInput;
  493. }
  494. set hideInput(hideInput) {
  495. this._hideInput = hideInput;
  496. this.update();
  497. }
  498. trySelectFirst() {
  499. if (this.autoFocusOnList) {
  500. if (!this.canSelectMany) {
  501. this.ui.list.focus(QuickInputListFocus.First);
  502. }
  503. }
  504. }
  505. show() {
  506. if (!this.visible) {
  507. this.visibleDisposables.add(this.ui.inputBox.onDidChange(value => {
  508. if (value === this.value) {
  509. return;
  510. }
  511. this._value = value;
  512. const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
  513. if (didFilter) {
  514. this.trySelectFirst();
  515. }
  516. this.onDidChangeValueEmitter.fire(value);
  517. }));
  518. this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => {
  519. if (!this.autoFocusOnList) {
  520. this.ui.list.clearFocus();
  521. }
  522. }));
  523. this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event) => {
  524. switch (event.keyCode) {
  525. case 18 /* DownArrow */:
  526. this.ui.list.focus(QuickInputListFocus.Next);
  527. if (this.canSelectMany) {
  528. this.ui.list.domFocus();
  529. }
  530. dom.EventHelper.stop(event, true);
  531. break;
  532. case 16 /* UpArrow */:
  533. if (this.ui.list.getFocusedElements().length) {
  534. this.ui.list.focus(QuickInputListFocus.Previous);
  535. }
  536. else {
  537. this.ui.list.focus(QuickInputListFocus.Last);
  538. }
  539. if (this.canSelectMany) {
  540. this.ui.list.domFocus();
  541. }
  542. dom.EventHelper.stop(event, true);
  543. break;
  544. case 12 /* PageDown */:
  545. this.ui.list.focus(QuickInputListFocus.NextPage);
  546. if (this.canSelectMany) {
  547. this.ui.list.domFocus();
  548. }
  549. dom.EventHelper.stop(event, true);
  550. break;
  551. case 11 /* PageUp */:
  552. this.ui.list.focus(QuickInputListFocus.PreviousPage);
  553. if (this.canSelectMany) {
  554. this.ui.list.domFocus();
  555. }
  556. dom.EventHelper.stop(event, true);
  557. break;
  558. case 17 /* RightArrow */:
  559. if (!this._canAcceptInBackground) {
  560. return; // needs to be enabled
  561. }
  562. if (!this.ui.inputBox.isSelectionAtEnd()) {
  563. return; // ensure input box selection at end
  564. }
  565. if (this.activeItems[0]) {
  566. this._selectedItems = [this.activeItems[0]];
  567. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  568. this.handleAccept(true);
  569. }
  570. break;
  571. case 14 /* Home */:
  572. if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
  573. this.ui.list.focus(QuickInputListFocus.First);
  574. dom.EventHelper.stop(event, true);
  575. }
  576. break;
  577. case 13 /* End */:
  578. if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
  579. this.ui.list.focus(QuickInputListFocus.Last);
  580. dom.EventHelper.stop(event, true);
  581. }
  582. break;
  583. }
  584. }));
  585. this.visibleDisposables.add(this.ui.onDidAccept(() => {
  586. if (this.canSelectMany) {
  587. // if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite
  588. // `_selectedItems`. In that case, we should emit one with an empty array to ensure that
  589. // `.selectedItems` is up to date.
  590. if (!this.ui.list.getCheckedElements().length) {
  591. this._selectedItems = [];
  592. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  593. }
  594. }
  595. else if (this.activeItems[0]) {
  596. // For single-select, we set `selectedItems` to the item that was accepted.
  597. this._selectedItems = [this.activeItems[0]];
  598. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  599. }
  600. this.handleAccept(false);
  601. }));
  602. this.visibleDisposables.add(this.ui.onDidCustom(() => {
  603. this.onDidCustomEmitter.fire();
  604. }));
  605. this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => {
  606. if (this.activeItemsUpdated) {
  607. return; // Expect another event.
  608. }
  609. if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
  610. return;
  611. }
  612. this._activeItems = focusedItems;
  613. this.onDidChangeActiveEmitter.fire(focusedItems);
  614. }));
  615. this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => {
  616. if (this.canSelectMany) {
  617. if (selectedItems.length) {
  618. this.ui.list.setSelectedElements([]);
  619. }
  620. return;
  621. }
  622. if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
  623. return;
  624. }
  625. this._selectedItems = selectedItems;
  626. this.onDidChangeSelectionEmitter.fire(selectedItems);
  627. if (selectedItems.length) {
  628. this.handleAccept(event instanceof MouseEvent && event.button === 1 /* mouse middle click */);
  629. }
  630. }));
  631. this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {
  632. if (!this.canSelectMany) {
  633. return;
  634. }
  635. if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
  636. return;
  637. }
  638. this._selectedItems = checkedItems;
  639. this.onDidChangeSelectionEmitter.fire(checkedItems);
  640. }));
  641. this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event)));
  642. this.visibleDisposables.add(this.registerQuickNavigation());
  643. this.valueSelectionUpdated = true;
  644. }
  645. super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.)
  646. }
  647. handleAccept(inBackground) {
  648. // Figure out veto via `onWillAccept` event
  649. let veto = false;
  650. this.onWillAcceptEmitter.fire({ veto: () => veto = true });
  651. // Continue with `onDidAccept` if no veto
  652. if (!veto) {
  653. this.onDidAcceptEmitter.fire({ inBackground });
  654. }
  655. }
  656. registerQuickNavigation() {
  657. return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
  658. if (this.canSelectMany || !this._quickNavigate) {
  659. return;
  660. }
  661. const keyboardEvent = new StandardKeyboardEvent(e);
  662. const keyCode = keyboardEvent.keyCode;
  663. // Select element when keys are pressed that signal it
  664. const quickNavKeys = this._quickNavigate.keybindings;
  665. const wasTriggerKeyPressed = quickNavKeys.some(k => {
  666. const [firstPart, chordPart] = k.getParts();
  667. if (chordPart) {
  668. return false;
  669. }
  670. if (firstPart.shiftKey && keyCode === 4 /* Shift */) {
  671. if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
  672. return false; // this is an optimistic check for the shift key being used to navigate back in quick input
  673. }
  674. return true;
  675. }
  676. if (firstPart.altKey && keyCode === 6 /* Alt */) {
  677. return true;
  678. }
  679. if (firstPart.ctrlKey && keyCode === 5 /* Ctrl */) {
  680. return true;
  681. }
  682. if (firstPart.metaKey && keyCode === 57 /* Meta */) {
  683. return true;
  684. }
  685. return false;
  686. });
  687. if (wasTriggerKeyPressed) {
  688. if (this.activeItems[0]) {
  689. this._selectedItems = [this.activeItems[0]];
  690. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  691. this.handleAccept(false);
  692. }
  693. // Unset quick navigate after press. It is only valid once
  694. // and should not result in any behaviour change afterwards
  695. // if the picker remains open because there was no active item
  696. this._quickNavigate = undefined;
  697. }
  698. });
  699. }
  700. update() {
  701. if (!this.visible) {
  702. return;
  703. }
  704. // store the scrollTop before it is reset
  705. const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0;
  706. const hideInput = !!this._hideInput && this._items.length > 0;
  707. this.ui.container.classList.toggle('hidden-input', hideInput && !this.description);
  708. const visibilities = {
  709. title: !!this.title || !!this.step || !!this.buttons.length,
  710. description: !!this.description,
  711. checkAll: this.canSelectMany && !this._hideCheckAll,
  712. checkBox: this.canSelectMany,
  713. inputBox: !hideInput,
  714. progressBar: !hideInput,
  715. visibleCount: true,
  716. count: this.canSelectMany,
  717. ok: this.ok === 'default' ? this.canSelectMany : this.ok,
  718. list: true,
  719. message: !!this.validationMessage,
  720. customButton: this.customButton
  721. };
  722. this.ui.setVisibilities(visibilities);
  723. super.update();
  724. if (this.ui.inputBox.value !== this.value) {
  725. this.ui.inputBox.value = this.value;
  726. }
  727. if (this.valueSelectionUpdated) {
  728. this.valueSelectionUpdated = false;
  729. this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
  730. }
  731. if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
  732. this.ui.inputBox.placeholder = (this.placeholder || '');
  733. }
  734. const ariaLabel = this.ariaLabel || this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;
  735. if (this.ui.inputBox.ariaLabel !== ariaLabel) {
  736. this.ui.inputBox.ariaLabel = ariaLabel;
  737. }
  738. this.ui.list.matchOnDescription = this.matchOnDescription;
  739. this.ui.list.matchOnDetail = this.matchOnDetail;
  740. this.ui.list.matchOnLabel = this.matchOnLabel;
  741. this.ui.list.sortByLabel = this.sortByLabel;
  742. if (this.itemsUpdated) {
  743. this.itemsUpdated = false;
  744. this.ui.list.setElements(this.items);
  745. this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
  746. this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
  747. this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
  748. this.ui.count.setCount(this.ui.list.getCheckedCount());
  749. switch (this._itemActivation) {
  750. case ItemActivation.NONE:
  751. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  752. break;
  753. case ItemActivation.SECOND:
  754. this.ui.list.focus(QuickInputListFocus.Second);
  755. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  756. break;
  757. case ItemActivation.LAST:
  758. this.ui.list.focus(QuickInputListFocus.Last);
  759. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  760. break;
  761. default:
  762. this.trySelectFirst();
  763. break;
  764. }
  765. }
  766. if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
  767. if (this.canSelectMany) {
  768. this.ui.list.clearFocus();
  769. }
  770. else {
  771. this.trySelectFirst();
  772. }
  773. }
  774. if (this.activeItemsUpdated) {
  775. this.activeItemsUpdated = false;
  776. this.activeItemsToConfirm = this._activeItems;
  777. this.ui.list.setFocusedElements(this.activeItems);
  778. if (this.activeItemsToConfirm === this._activeItems) {
  779. this.activeItemsToConfirm = null;
  780. }
  781. }
  782. if (this.selectedItemsUpdated) {
  783. this.selectedItemsUpdated = false;
  784. this.selectedItemsToConfirm = this._selectedItems;
  785. if (this.canSelectMany) {
  786. this.ui.list.setCheckedElements(this.selectedItems);
  787. }
  788. else {
  789. this.ui.list.setSelectedElements(this.selectedItems);
  790. }
  791. if (this.selectedItemsToConfirm === this._selectedItems) {
  792. this.selectedItemsToConfirm = null;
  793. }
  794. }
  795. this.ui.customButton.label = this.customLabel || '';
  796. this.ui.customButton.element.title = this.customHover || '';
  797. this.ui.setComboboxAccessibility(true);
  798. if (!visibilities.inputBox) {
  799. // we need to move focus into the tree to detect keybindings
  800. // properly when the input box is not visible (quick nav)
  801. this.ui.list.domFocus();
  802. // Focus the first element in the list if multiselect is enabled
  803. if (this.canSelectMany) {
  804. this.ui.list.focus(QuickInputListFocus.First);
  805. }
  806. }
  807. // Set the scroll position to what it was before updating the items
  808. if (this.keepScrollPosition) {
  809. this.scrollTop = scrollTopBefore;
  810. }
  811. }
  812. }
  813. QuickPick.DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
  814. export class QuickInputController extends Disposable {
  815. constructor(options) {
  816. super();
  817. this.options = options;
  818. this.comboboxAccessibility = false;
  819. this.enabled = true;
  820. this.onDidAcceptEmitter = this._register(new Emitter());
  821. this.onDidCustomEmitter = this._register(new Emitter());
  822. this.onDidTriggerButtonEmitter = this._register(new Emitter());
  823. this.keyMods = { ctrlCmd: false, alt: false };
  824. this.controller = null;
  825. this.onShowEmitter = this._register(new Emitter());
  826. this.onShow = this.onShowEmitter.event;
  827. this.onHideEmitter = this._register(new Emitter());
  828. this.onHide = this.onHideEmitter.event;
  829. this.idPrefix = options.idPrefix;
  830. this.parentElement = options.container;
  831. this.styles = options.styles;
  832. this.registerKeyModsListeners();
  833. }
  834. registerKeyModsListeners() {
  835. const listener = (e) => {
  836. this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey;
  837. this.keyMods.alt = e.altKey;
  838. };
  839. this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, listener, true));
  840. this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, listener, true));
  841. this._register(dom.addDisposableListener(window, dom.EventType.MOUSE_DOWN, listener, true));
  842. }
  843. getUI() {
  844. if (this.ui) {
  845. return this.ui;
  846. }
  847. const container = dom.append(this.parentElement, $('.quick-input-widget.show-file-icons'));
  848. container.tabIndex = -1;
  849. container.style.display = 'none';
  850. const styleSheet = dom.createStyleSheet(container);
  851. const titleBar = dom.append(container, $('.quick-input-titlebar'));
  852. const leftActionBar = this._register(new ActionBar(titleBar));
  853. leftActionBar.domNode.classList.add('quick-input-left-action-bar');
  854. const title = dom.append(titleBar, $('.quick-input-title'));
  855. const rightActionBar = this._register(new ActionBar(titleBar));
  856. rightActionBar.domNode.classList.add('quick-input-right-action-bar');
  857. const description1 = dom.append(container, $('.quick-input-description'));
  858. const headerContainer = dom.append(container, $('.quick-input-header'));
  859. const checkAll = dom.append(headerContainer, $('input.quick-input-check-all'));
  860. checkAll.type = 'checkbox';
  861. this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => {
  862. const checked = checkAll.checked;
  863. list.setAllVisibleChecked(checked);
  864. }));
  865. this._register(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => {
  866. if (e.x || e.y) { // Avoid 'click' triggered by 'space'...
  867. inputBox.setFocus();
  868. }
  869. }));
  870. const description2 = dom.append(headerContainer, $('.quick-input-description'));
  871. const extraContainer = dom.append(headerContainer, $('.quick-input-and-message'));
  872. const filterContainer = dom.append(extraContainer, $('.quick-input-filter'));
  873. const inputBox = this._register(new QuickInputBox(filterContainer));
  874. inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`);
  875. const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count'));
  876. visibleCountContainer.setAttribute('aria-live', 'polite');
  877. visibleCountContainer.setAttribute('aria-atomic', 'true');
  878. const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") });
  879. const countContainer = dom.append(filterContainer, $('.quick-input-count'));
  880. countContainer.setAttribute('aria-live', 'polite');
  881. const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") });
  882. const okContainer = dom.append(headerContainer, $('.quick-input-action'));
  883. const ok = new Button(okContainer);
  884. ok.label = localize('ok', "OK");
  885. this._register(ok.onDidClick(e => {
  886. this.onDidAcceptEmitter.fire();
  887. }));
  888. const customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
  889. const customButton = new Button(customButtonContainer);
  890. customButton.label = localize('custom', "Custom");
  891. this._register(customButton.onDidClick(e => {
  892. this.onDidCustomEmitter.fire();
  893. }));
  894. const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`));
  895. const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options));
  896. this._register(list.onChangedAllVisibleChecked(checked => {
  897. checkAll.checked = checked;
  898. }));
  899. this._register(list.onChangedVisibleCount(c => {
  900. visibleCount.setCount(c);
  901. }));
  902. this._register(list.onChangedCheckedCount(c => {
  903. count.setCount(c);
  904. }));
  905. this._register(list.onLeave(() => {
  906. // Defer to avoid the input field reacting to the triggering key.
  907. setTimeout(() => {
  908. inputBox.setFocus();
  909. if (this.controller instanceof QuickPick && this.controller.canSelectMany) {
  910. list.clearFocus();
  911. }
  912. }, 0);
  913. }));
  914. this._register(list.onDidChangeFocus(() => {
  915. if (this.comboboxAccessibility) {
  916. this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || '');
  917. }
  918. }));
  919. const progressBar = new ProgressBar(container);
  920. progressBar.getContainer().classList.add('quick-input-progress');
  921. const focusTracker = dom.trackFocus(container);
  922. this._register(focusTracker);
  923. this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => {
  924. this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined;
  925. }, true));
  926. this._register(focusTracker.onDidBlur(() => {
  927. if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
  928. this.hide(QuickInputHideReason.Blur);
  929. }
  930. this.previousFocusElement = undefined;
  931. }));
  932. this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e) => {
  933. inputBox.setFocus();
  934. }));
  935. this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e) => {
  936. const event = new StandardKeyboardEvent(e);
  937. switch (event.keyCode) {
  938. case 3 /* Enter */:
  939. dom.EventHelper.stop(e, true);
  940. this.onDidAcceptEmitter.fire();
  941. break;
  942. case 9 /* Escape */:
  943. dom.EventHelper.stop(e, true);
  944. this.hide(QuickInputHideReason.Gesture);
  945. break;
  946. case 2 /* Tab */:
  947. if (!event.altKey && !event.ctrlKey && !event.metaKey) {
  948. const selectors = ['.action-label.codicon'];
  949. if (container.classList.contains('show-checkboxes')) {
  950. selectors.push('input');
  951. }
  952. else {
  953. selectors.push('input[type=text]');
  954. }
  955. if (this.getUI().list.isDisplayed()) {
  956. selectors.push('.monaco-list');
  957. }
  958. const stops = container.querySelectorAll(selectors.join(', '));
  959. if (event.shiftKey && event.target === stops[0]) {
  960. dom.EventHelper.stop(e, true);
  961. stops[stops.length - 1].focus();
  962. }
  963. else if (!event.shiftKey && event.target === stops[stops.length - 1]) {
  964. dom.EventHelper.stop(e, true);
  965. stops[0].focus();
  966. }
  967. }
  968. break;
  969. }
  970. }));
  971. this.ui = {
  972. container,
  973. styleSheet,
  974. leftActionBar,
  975. titleBar,
  976. title,
  977. description1,
  978. description2,
  979. rightActionBar,
  980. checkAll,
  981. filterContainer,
  982. inputBox,
  983. visibleCountContainer,
  984. visibleCount,
  985. countContainer,
  986. count,
  987. okContainer,
  988. ok,
  989. message,
  990. customButtonContainer,
  991. customButton,
  992. list,
  993. progressBar,
  994. onDidAccept: this.onDidAcceptEmitter.event,
  995. onDidCustom: this.onDidCustomEmitter.event,
  996. onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
  997. ignoreFocusOut: false,
  998. keyMods: this.keyMods,
  999. isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(),
  1000. show: controller => this.show(controller),
  1001. hide: () => this.hide(),
  1002. setVisibilities: visibilities => this.setVisibilities(visibilities),
  1003. setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled),
  1004. setEnabled: enabled => this.setEnabled(enabled),
  1005. setContextKey: contextKey => this.options.setContextKey(contextKey),
  1006. };
  1007. this.updateStyles();
  1008. return this.ui;
  1009. }
  1010. pick(picks, options = {}, token = CancellationToken.None) {
  1011. return new Promise((doResolve, reject) => {
  1012. let resolve = (result) => {
  1013. resolve = doResolve;
  1014. if (options.onKeyMods) {
  1015. options.onKeyMods(input.keyMods);
  1016. }
  1017. doResolve(result);
  1018. };
  1019. if (token.isCancellationRequested) {
  1020. resolve(undefined);
  1021. return;
  1022. }
  1023. const input = this.createQuickPick();
  1024. let activeItem;
  1025. const disposables = [
  1026. input,
  1027. input.onDidAccept(() => {
  1028. if (input.canSelectMany) {
  1029. resolve(input.selectedItems.slice());
  1030. input.hide();
  1031. }
  1032. else {
  1033. const result = input.activeItems[0];
  1034. if (result) {
  1035. resolve(result);
  1036. input.hide();
  1037. }
  1038. }
  1039. }),
  1040. input.onDidChangeActive(items => {
  1041. const focused = items[0];
  1042. if (focused && options.onDidFocus) {
  1043. options.onDidFocus(focused);
  1044. }
  1045. }),
  1046. input.onDidChangeSelection(items => {
  1047. if (!input.canSelectMany) {
  1048. const result = items[0];
  1049. if (result) {
  1050. resolve(result);
  1051. input.hide();
  1052. }
  1053. }
  1054. }),
  1055. input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton(Object.assign(Object.assign({}, event), { removeItem: () => {
  1056. const index = input.items.indexOf(event.item);
  1057. if (index !== -1) {
  1058. const items = input.items.slice();
  1059. const removed = items.splice(index, 1);
  1060. const activeItems = input.activeItems.filter(activeItem => activeItem !== removed[0]);
  1061. const keepScrollPositionBefore = input.keepScrollPosition;
  1062. input.keepScrollPosition = true;
  1063. input.items = items;
  1064. if (activeItems) {
  1065. input.activeItems = activeItems;
  1066. }
  1067. input.keepScrollPosition = keepScrollPositionBefore;
  1068. }
  1069. } }))),
  1070. input.onDidChangeValue(value => {
  1071. if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) {
  1072. input.activeItems = [activeItem];
  1073. }
  1074. }),
  1075. token.onCancellationRequested(() => {
  1076. input.hide();
  1077. }),
  1078. input.onDidHide(() => {
  1079. dispose(disposables);
  1080. resolve(undefined);
  1081. }),
  1082. ];
  1083. input.title = options.title;
  1084. input.canSelectMany = !!options.canPickMany;
  1085. input.placeholder = options.placeHolder;
  1086. input.ignoreFocusOut = !!options.ignoreFocusLost;
  1087. input.matchOnDescription = !!options.matchOnDescription;
  1088. input.matchOnDetail = !!options.matchOnDetail;
  1089. input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true
  1090. input.autoFocusOnList = (options.autoFocusOnList === undefined) || options.autoFocusOnList; // default to true
  1091. input.quickNavigate = options.quickNavigate;
  1092. input.contextKey = options.contextKey;
  1093. input.busy = true;
  1094. Promise.all([picks, options.activeItem])
  1095. .then(([items, _activeItem]) => {
  1096. activeItem = _activeItem;
  1097. input.busy = false;
  1098. input.items = items;
  1099. if (input.canSelectMany) {
  1100. input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked);
  1101. }
  1102. if (activeItem) {
  1103. input.activeItems = [activeItem];
  1104. }
  1105. });
  1106. input.show();
  1107. Promise.resolve(picks).then(undefined, err => {
  1108. reject(err);
  1109. input.hide();
  1110. });
  1111. });
  1112. }
  1113. createQuickPick() {
  1114. const ui = this.getUI();
  1115. return new QuickPick(ui);
  1116. }
  1117. show(controller) {
  1118. const ui = this.getUI();
  1119. this.onShowEmitter.fire();
  1120. const oldController = this.controller;
  1121. this.controller = controller;
  1122. if (oldController) {
  1123. oldController.didHide();
  1124. }
  1125. this.setEnabled(true);
  1126. ui.leftActionBar.clear();
  1127. ui.title.textContent = '';
  1128. ui.description1.textContent = '';
  1129. ui.description2.textContent = '';
  1130. ui.rightActionBar.clear();
  1131. ui.checkAll.checked = false;
  1132. // ui.inputBox.value = ''; Avoid triggering an event.
  1133. ui.inputBox.placeholder = '';
  1134. ui.inputBox.password = false;
  1135. ui.inputBox.showDecoration(Severity.Ignore);
  1136. ui.visibleCount.setCount(0);
  1137. ui.count.setCount(0);
  1138. dom.reset(ui.message);
  1139. ui.progressBar.stop();
  1140. ui.list.setElements([]);
  1141. ui.list.matchOnDescription = false;
  1142. ui.list.matchOnDetail = false;
  1143. ui.list.matchOnLabel = true;
  1144. ui.list.sortByLabel = true;
  1145. ui.ignoreFocusOut = false;
  1146. this.setComboboxAccessibility(false);
  1147. ui.inputBox.ariaLabel = '';
  1148. const backKeybindingLabel = this.options.backKeybindingLabel();
  1149. backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back");
  1150. ui.container.style.display = '';
  1151. this.updateLayout();
  1152. ui.inputBox.setFocus();
  1153. }
  1154. setVisibilities(visibilities) {
  1155. const ui = this.getUI();
  1156. ui.title.style.display = visibilities.title ? '' : 'none';
  1157. ui.description1.style.display = visibilities.description && (visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
  1158. ui.description2.style.display = visibilities.description && !(visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
  1159. ui.checkAll.style.display = visibilities.checkAll ? '' : 'none';
  1160. ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none';
  1161. ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
  1162. ui.countContainer.style.display = visibilities.count ? '' : 'none';
  1163. ui.okContainer.style.display = visibilities.ok ? '' : 'none';
  1164. ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none';
  1165. ui.message.style.display = visibilities.message ? '' : 'none';
  1166. ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none';
  1167. ui.list.display(!!visibilities.list);
  1168. ui.container.classList[visibilities.checkBox ? 'add' : 'remove']('show-checkboxes');
  1169. this.updateLayout(); // TODO
  1170. }
  1171. setComboboxAccessibility(enabled) {
  1172. if (enabled !== this.comboboxAccessibility) {
  1173. const ui = this.getUI();
  1174. this.comboboxAccessibility = enabled;
  1175. if (this.comboboxAccessibility) {
  1176. ui.inputBox.setAttribute('role', 'combobox');
  1177. ui.inputBox.setAttribute('aria-haspopup', 'true');
  1178. ui.inputBox.setAttribute('aria-autocomplete', 'list');
  1179. ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || '');
  1180. }
  1181. else {
  1182. ui.inputBox.removeAttribute('role');
  1183. ui.inputBox.removeAttribute('aria-haspopup');
  1184. ui.inputBox.removeAttribute('aria-autocomplete');
  1185. ui.inputBox.removeAttribute('aria-activedescendant');
  1186. }
  1187. }
  1188. }
  1189. setEnabled(enabled) {
  1190. if (enabled !== this.enabled) {
  1191. this.enabled = enabled;
  1192. for (const item of this.getUI().leftActionBar.viewItems) {
  1193. item.getAction().enabled = enabled;
  1194. }
  1195. for (const item of this.getUI().rightActionBar.viewItems) {
  1196. item.getAction().enabled = enabled;
  1197. }
  1198. this.getUI().checkAll.disabled = !enabled;
  1199. // this.getUI().inputBox.enabled = enabled; Avoid loosing focus.
  1200. this.getUI().ok.enabled = enabled;
  1201. this.getUI().list.enabled = enabled;
  1202. }
  1203. }
  1204. hide(reason) {
  1205. var _a;
  1206. const controller = this.controller;
  1207. if (controller) {
  1208. const focusChanged = !((_a = this.ui) === null || _a === void 0 ? void 0 : _a.container.contains(document.activeElement));
  1209. this.controller = null;
  1210. this.onHideEmitter.fire();
  1211. this.getUI().container.style.display = 'none';
  1212. if (!focusChanged) {
  1213. let currentElement = this.previousFocusElement;
  1214. while (currentElement && !currentElement.offsetParent) {
  1215. currentElement = withNullAsUndefined(currentElement.parentElement);
  1216. }
  1217. if (currentElement === null || currentElement === void 0 ? void 0 : currentElement.offsetParent) {
  1218. currentElement.focus();
  1219. this.previousFocusElement = undefined;
  1220. }
  1221. else {
  1222. this.options.returnFocus();
  1223. }
  1224. }
  1225. controller.didHide(reason);
  1226. }
  1227. }
  1228. layout(dimension, titleBarOffset) {
  1229. this.dimension = dimension;
  1230. this.titleBarOffset = titleBarOffset;
  1231. this.updateLayout();
  1232. }
  1233. updateLayout() {
  1234. if (this.ui) {
  1235. this.ui.container.style.top = `${this.titleBarOffset}px`;
  1236. const style = this.ui.container.style;
  1237. const width = Math.min(this.dimension.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH);
  1238. style.width = width + 'px';
  1239. style.marginLeft = '-' + (width / 2) + 'px';
  1240. this.ui.inputBox.layout();
  1241. this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
  1242. }
  1243. }
  1244. applyStyles(styles) {
  1245. this.styles = styles;
  1246. this.updateStyles();
  1247. }
  1248. updateStyles() {
  1249. if (this.ui) {
  1250. const { quickInputTitleBackground, quickInputBackground, quickInputForeground, contrastBorder, widgetShadow, } = this.styles.widget;
  1251. this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ? quickInputTitleBackground.toString() : '';
  1252. this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : '';
  1253. this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : '';
  1254. this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : '';
  1255. this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
  1256. this.ui.inputBox.style(this.styles.inputBox);
  1257. this.ui.count.style(this.styles.countBadge);
  1258. this.ui.ok.style(this.styles.button);
  1259. this.ui.customButton.style(this.styles.button);
  1260. this.ui.progressBar.style(this.styles.progressBar);
  1261. this.ui.list.style(this.styles.list);
  1262. const content = [];
  1263. if (this.styles.list.pickerGroupBorder) {
  1264. content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.list.pickerGroupBorder}; }`);
  1265. }
  1266. if (this.styles.list.pickerGroupForeground) {
  1267. content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`);
  1268. }
  1269. if (this.styles.keybindingLabel.keybindingLabelBackground ||
  1270. this.styles.keybindingLabel.keybindingLabelBorder ||
  1271. this.styles.keybindingLabel.keybindingLabelBottomBorder ||
  1272. this.styles.keybindingLabel.keybindingLabelShadow ||
  1273. this.styles.keybindingLabel.keybindingLabelForeground) {
  1274. content.push('.quick-input-list .monaco-keybinding > .monaco-keybinding-key {');
  1275. if (this.styles.keybindingLabel.keybindingLabelBackground) {
  1276. content.push(`background-color: ${this.styles.keybindingLabel.keybindingLabelBackground};`);
  1277. }
  1278. if (this.styles.keybindingLabel.keybindingLabelBorder) {
  1279. // Order matters here. `border-color` must come before `border-bottom-color`.
  1280. content.push(`border-color: ${this.styles.keybindingLabel.keybindingLabelBorder};`);
  1281. }
  1282. if (this.styles.keybindingLabel.keybindingLabelBottomBorder) {
  1283. content.push(`border-bottom-color: ${this.styles.keybindingLabel.keybindingLabelBottomBorder};`);
  1284. }
  1285. if (this.styles.keybindingLabel.keybindingLabelShadow) {
  1286. content.push(`box-shadow: inset 0 -1px 0 ${this.styles.keybindingLabel.keybindingLabelShadow};`);
  1287. }
  1288. if (this.styles.keybindingLabel.keybindingLabelForeground) {
  1289. content.push(`color: ${this.styles.keybindingLabel.keybindingLabelForeground};`);
  1290. }
  1291. content.push('}');
  1292. }
  1293. const newStyles = content.join('\n');
  1294. if (newStyles !== this.ui.styleSheet.textContent) {
  1295. this.ui.styleSheet.textContent = newStyles;
  1296. }
  1297. }
  1298. }
  1299. }
  1300. QuickInputController.MAX_WIDTH = 600; // Max total width of quick input widget