c83030aa4d7fab649cec419dba9be71b6bbf2b99c740a24b71e40ab3b7ddd1817db9f2353667b403d895f6be7cd8d5da713c7ab1a8e1f1332adb56a3109625 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  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.marginBottom = '-2px';
  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.marginBottom = '';
  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._matchOnLabelMode = 'fuzzy';
  304. this._sortByLabel = true;
  305. this._autoFocusOnList = true;
  306. this._keepScrollPosition = false;
  307. this._itemActivation = this.ui.isScreenReaderOptimized() ? ItemActivation.NONE /* https://github.com/microsoft/vscode/issues/57501 */ : ItemActivation.FIRST;
  308. this._activeItems = [];
  309. this.activeItemsUpdated = false;
  310. this.activeItemsToConfirm = [];
  311. this.onDidChangeActiveEmitter = this._register(new Emitter());
  312. this._selectedItems = [];
  313. this.selectedItemsUpdated = false;
  314. this.selectedItemsToConfirm = [];
  315. this.onDidChangeSelectionEmitter = this._register(new Emitter());
  316. this.onDidTriggerItemButtonEmitter = this._register(new Emitter());
  317. this.valueSelectionUpdated = true;
  318. this._ok = 'default';
  319. this._customButton = false;
  320. this.filterValue = (value) => value;
  321. this.onDidChangeValue = this.onDidChangeValueEmitter.event;
  322. this.onWillAccept = this.onWillAcceptEmitter.event;
  323. this.onDidAccept = this.onDidAcceptEmitter.event;
  324. this.onDidChangeActive = this.onDidChangeActiveEmitter.event;
  325. this.onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
  326. this.onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
  327. }
  328. get quickNavigate() {
  329. return this._quickNavigate;
  330. }
  331. set quickNavigate(quickNavigate) {
  332. this._quickNavigate = quickNavigate;
  333. this.update();
  334. }
  335. get value() {
  336. return this._value;
  337. }
  338. set value(value) {
  339. this.doSetValue(value);
  340. }
  341. doSetValue(value, skipUpdate) {
  342. if (this._value !== value) {
  343. this._value = value;
  344. if (!skipUpdate) {
  345. this.update();
  346. }
  347. if (this.visible) {
  348. const didFilter = this.ui.list.filter(this.filterValue(this._value));
  349. if (didFilter) {
  350. this.trySelectFirst();
  351. }
  352. }
  353. this.onDidChangeValueEmitter.fire(this._value);
  354. }
  355. }
  356. set ariaLabel(ariaLabel) {
  357. this._ariaLabel = ariaLabel;
  358. this.update();
  359. }
  360. get ariaLabel() {
  361. return this._ariaLabel;
  362. }
  363. get placeholder() {
  364. return this._placeholder;
  365. }
  366. set placeholder(placeholder) {
  367. this._placeholder = placeholder;
  368. this.update();
  369. }
  370. get items() {
  371. return this._items;
  372. }
  373. get scrollTop() {
  374. return this.ui.list.scrollTop;
  375. }
  376. set scrollTop(scrollTop) {
  377. this.ui.list.scrollTop = scrollTop;
  378. }
  379. set items(items) {
  380. this._items = items;
  381. this.itemsUpdated = true;
  382. this.update();
  383. }
  384. get canSelectMany() {
  385. return this._canSelectMany;
  386. }
  387. set canSelectMany(canSelectMany) {
  388. this._canSelectMany = canSelectMany;
  389. this.update();
  390. }
  391. get canAcceptInBackground() {
  392. return this._canAcceptInBackground;
  393. }
  394. set canAcceptInBackground(canAcceptInBackground) {
  395. this._canAcceptInBackground = canAcceptInBackground;
  396. }
  397. get matchOnDescription() {
  398. return this._matchOnDescription;
  399. }
  400. set matchOnDescription(matchOnDescription) {
  401. this._matchOnDescription = matchOnDescription;
  402. this.update();
  403. }
  404. get matchOnDetail() {
  405. return this._matchOnDetail;
  406. }
  407. set matchOnDetail(matchOnDetail) {
  408. this._matchOnDetail = matchOnDetail;
  409. this.update();
  410. }
  411. get matchOnLabel() {
  412. return this._matchOnLabel;
  413. }
  414. set matchOnLabel(matchOnLabel) {
  415. this._matchOnLabel = matchOnLabel;
  416. this.update();
  417. }
  418. get matchOnLabelMode() {
  419. return this._matchOnLabelMode;
  420. }
  421. set matchOnLabelMode(matchOnLabelMode) {
  422. this._matchOnLabelMode = matchOnLabelMode;
  423. this.update();
  424. }
  425. get sortByLabel() {
  426. return this._sortByLabel;
  427. }
  428. set sortByLabel(sortByLabel) {
  429. this._sortByLabel = sortByLabel;
  430. this.update();
  431. }
  432. get autoFocusOnList() {
  433. return this._autoFocusOnList;
  434. }
  435. set autoFocusOnList(autoFocusOnList) {
  436. this._autoFocusOnList = autoFocusOnList;
  437. this.update();
  438. }
  439. get keepScrollPosition() {
  440. return this._keepScrollPosition;
  441. }
  442. set keepScrollPosition(keepScrollPosition) {
  443. this._keepScrollPosition = keepScrollPosition;
  444. }
  445. get itemActivation() {
  446. return this._itemActivation;
  447. }
  448. set itemActivation(itemActivation) {
  449. this._itemActivation = itemActivation;
  450. }
  451. get activeItems() {
  452. return this._activeItems;
  453. }
  454. set activeItems(activeItems) {
  455. this._activeItems = activeItems;
  456. this.activeItemsUpdated = true;
  457. this.update();
  458. }
  459. get selectedItems() {
  460. return this._selectedItems;
  461. }
  462. set selectedItems(selectedItems) {
  463. this._selectedItems = selectedItems;
  464. this.selectedItemsUpdated = true;
  465. this.update();
  466. }
  467. get keyMods() {
  468. if (this._quickNavigate) {
  469. // Disable keyMods when quick navigate is enabled
  470. // because in this model the interaction is purely
  471. // keyboard driven and Ctrl/Alt are typically
  472. // pressed and hold during this interaction.
  473. return NO_KEY_MODS;
  474. }
  475. return this.ui.keyMods;
  476. }
  477. set valueSelection(valueSelection) {
  478. this._valueSelection = valueSelection;
  479. this.valueSelectionUpdated = true;
  480. this.update();
  481. }
  482. get customButton() {
  483. return this._customButton;
  484. }
  485. set customButton(showCustomButton) {
  486. this._customButton = showCustomButton;
  487. this.update();
  488. }
  489. get customLabel() {
  490. return this._customButtonLabel;
  491. }
  492. set customLabel(label) {
  493. this._customButtonLabel = label;
  494. this.update();
  495. }
  496. get customHover() {
  497. return this._customButtonHover;
  498. }
  499. set customHover(hover) {
  500. this._customButtonHover = hover;
  501. this.update();
  502. }
  503. get ok() {
  504. return this._ok;
  505. }
  506. set ok(showOkButton) {
  507. this._ok = showOkButton;
  508. this.update();
  509. }
  510. get hideInput() {
  511. return !!this._hideInput;
  512. }
  513. set hideInput(hideInput) {
  514. this._hideInput = hideInput;
  515. this.update();
  516. }
  517. trySelectFirst() {
  518. if (this.autoFocusOnList) {
  519. if (!this.canSelectMany) {
  520. this.ui.list.focus(QuickInputListFocus.First);
  521. }
  522. }
  523. }
  524. show() {
  525. if (!this.visible) {
  526. this.visibleDisposables.add(this.ui.inputBox.onDidChange(value => {
  527. this.doSetValue(value, true /* skip update since this originates from the UI */);
  528. }));
  529. this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => {
  530. if (!this.autoFocusOnList) {
  531. this.ui.list.clearFocus();
  532. }
  533. }));
  534. this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event) => {
  535. switch (event.keyCode) {
  536. case 18 /* KeyCode.DownArrow */:
  537. this.ui.list.focus(QuickInputListFocus.Next);
  538. if (this.canSelectMany) {
  539. this.ui.list.domFocus();
  540. }
  541. dom.EventHelper.stop(event, true);
  542. break;
  543. case 16 /* KeyCode.UpArrow */:
  544. if (this.ui.list.getFocusedElements().length) {
  545. this.ui.list.focus(QuickInputListFocus.Previous);
  546. }
  547. else {
  548. this.ui.list.focus(QuickInputListFocus.Last);
  549. }
  550. if (this.canSelectMany) {
  551. this.ui.list.domFocus();
  552. }
  553. dom.EventHelper.stop(event, true);
  554. break;
  555. case 12 /* KeyCode.PageDown */:
  556. this.ui.list.focus(QuickInputListFocus.NextPage);
  557. if (this.canSelectMany) {
  558. this.ui.list.domFocus();
  559. }
  560. dom.EventHelper.stop(event, true);
  561. break;
  562. case 11 /* KeyCode.PageUp */:
  563. this.ui.list.focus(QuickInputListFocus.PreviousPage);
  564. if (this.canSelectMany) {
  565. this.ui.list.domFocus();
  566. }
  567. dom.EventHelper.stop(event, true);
  568. break;
  569. case 17 /* KeyCode.RightArrow */:
  570. if (!this._canAcceptInBackground) {
  571. return; // needs to be enabled
  572. }
  573. if (!this.ui.inputBox.isSelectionAtEnd()) {
  574. return; // ensure input box selection at end
  575. }
  576. if (this.activeItems[0]) {
  577. this._selectedItems = [this.activeItems[0]];
  578. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  579. this.handleAccept(true);
  580. }
  581. break;
  582. case 14 /* KeyCode.Home */:
  583. if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
  584. this.ui.list.focus(QuickInputListFocus.First);
  585. dom.EventHelper.stop(event, true);
  586. }
  587. break;
  588. case 13 /* KeyCode.End */:
  589. if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) {
  590. this.ui.list.focus(QuickInputListFocus.Last);
  591. dom.EventHelper.stop(event, true);
  592. }
  593. break;
  594. }
  595. }));
  596. this.visibleDisposables.add(this.ui.onDidAccept(() => {
  597. if (this.canSelectMany) {
  598. // if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite
  599. // `_selectedItems`. In that case, we should emit one with an empty array to ensure that
  600. // `.selectedItems` is up to date.
  601. if (!this.ui.list.getCheckedElements().length) {
  602. this._selectedItems = [];
  603. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  604. }
  605. }
  606. else if (this.activeItems[0]) {
  607. // For single-select, we set `selectedItems` to the item that was accepted.
  608. this._selectedItems = [this.activeItems[0]];
  609. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  610. }
  611. this.handleAccept(false);
  612. }));
  613. this.visibleDisposables.add(this.ui.onDidCustom(() => {
  614. this.onDidCustomEmitter.fire();
  615. }));
  616. this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => {
  617. if (this.activeItemsUpdated) {
  618. return; // Expect another event.
  619. }
  620. if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) {
  621. return;
  622. }
  623. this._activeItems = focusedItems;
  624. this.onDidChangeActiveEmitter.fire(focusedItems);
  625. }));
  626. this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => {
  627. if (this.canSelectMany) {
  628. if (selectedItems.length) {
  629. this.ui.list.setSelectedElements([]);
  630. }
  631. return;
  632. }
  633. if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
  634. return;
  635. }
  636. this._selectedItems = selectedItems;
  637. this.onDidChangeSelectionEmitter.fire(selectedItems);
  638. if (selectedItems.length) {
  639. this.handleAccept(event instanceof MouseEvent && event.button === 1 /* mouse middle click */);
  640. }
  641. }));
  642. this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => {
  643. if (!this.canSelectMany) {
  644. return;
  645. }
  646. if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) {
  647. return;
  648. }
  649. this._selectedItems = checkedItems;
  650. this.onDidChangeSelectionEmitter.fire(checkedItems);
  651. }));
  652. this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event)));
  653. this.visibleDisposables.add(this.registerQuickNavigation());
  654. this.valueSelectionUpdated = true;
  655. }
  656. super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.)
  657. }
  658. handleAccept(inBackground) {
  659. // Figure out veto via `onWillAccept` event
  660. let veto = false;
  661. this.onWillAcceptEmitter.fire({ veto: () => veto = true });
  662. // Continue with `onDidAccept` if no veto
  663. if (!veto) {
  664. this.onDidAcceptEmitter.fire({ inBackground });
  665. }
  666. }
  667. registerQuickNavigation() {
  668. return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
  669. if (this.canSelectMany || !this._quickNavigate) {
  670. return;
  671. }
  672. const keyboardEvent = new StandardKeyboardEvent(e);
  673. const keyCode = keyboardEvent.keyCode;
  674. // Select element when keys are pressed that signal it
  675. const quickNavKeys = this._quickNavigate.keybindings;
  676. const wasTriggerKeyPressed = quickNavKeys.some(k => {
  677. const [firstPart, chordPart] = k.getParts();
  678. if (chordPart) {
  679. return false;
  680. }
  681. if (firstPart.shiftKey && keyCode === 4 /* KeyCode.Shift */) {
  682. if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
  683. return false; // this is an optimistic check for the shift key being used to navigate back in quick input
  684. }
  685. return true;
  686. }
  687. if (firstPart.altKey && keyCode === 6 /* KeyCode.Alt */) {
  688. return true;
  689. }
  690. if (firstPart.ctrlKey && keyCode === 5 /* KeyCode.Ctrl */) {
  691. return true;
  692. }
  693. if (firstPart.metaKey && keyCode === 57 /* KeyCode.Meta */) {
  694. return true;
  695. }
  696. return false;
  697. });
  698. if (wasTriggerKeyPressed) {
  699. if (this.activeItems[0]) {
  700. this._selectedItems = [this.activeItems[0]];
  701. this.onDidChangeSelectionEmitter.fire(this.selectedItems);
  702. this.handleAccept(false);
  703. }
  704. // Unset quick navigate after press. It is only valid once
  705. // and should not result in any behaviour change afterwards
  706. // if the picker remains open because there was no active item
  707. this._quickNavigate = undefined;
  708. }
  709. });
  710. }
  711. update() {
  712. if (!this.visible) {
  713. return;
  714. }
  715. // store the scrollTop before it is reset
  716. const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0;
  717. const hideInput = !!this._hideInput && this._items.length > 0;
  718. this.ui.container.classList.toggle('hidden-input', hideInput && !this.description);
  719. const visibilities = {
  720. title: !!this.title || !!this.step || !!this.buttons.length,
  721. description: !!this.description,
  722. checkAll: this.canSelectMany && !this._hideCheckAll,
  723. checkBox: this.canSelectMany,
  724. inputBox: !hideInput,
  725. progressBar: !hideInput,
  726. visibleCount: true,
  727. count: this.canSelectMany,
  728. ok: this.ok === 'default' ? this.canSelectMany : this.ok,
  729. list: true,
  730. message: !!this.validationMessage,
  731. customButton: this.customButton
  732. };
  733. this.ui.setVisibilities(visibilities);
  734. super.update();
  735. if (this.ui.inputBox.value !== this.value) {
  736. this.ui.inputBox.value = this.value;
  737. }
  738. if (this.valueSelectionUpdated) {
  739. this.valueSelectionUpdated = false;
  740. this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
  741. }
  742. if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
  743. this.ui.inputBox.placeholder = (this.placeholder || '');
  744. }
  745. let ariaLabel = this.ariaLabel;
  746. if (!ariaLabel) {
  747. ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL;
  748. // If we have a title, include it in the aria label.
  749. if (this.title) {
  750. ariaLabel += ` - ${this.title}`;
  751. }
  752. }
  753. if (this.ui.inputBox.ariaLabel !== ariaLabel) {
  754. this.ui.inputBox.ariaLabel = ariaLabel;
  755. }
  756. this.ui.list.matchOnDescription = this.matchOnDescription;
  757. this.ui.list.matchOnDetail = this.matchOnDetail;
  758. this.ui.list.matchOnLabel = this.matchOnLabel;
  759. this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
  760. this.ui.list.sortByLabel = this.sortByLabel;
  761. if (this.itemsUpdated) {
  762. this.itemsUpdated = false;
  763. this.ui.list.setElements(this.items);
  764. this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
  765. this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
  766. this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
  767. this.ui.count.setCount(this.ui.list.getCheckedCount());
  768. switch (this._itemActivation) {
  769. case ItemActivation.NONE:
  770. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  771. break;
  772. case ItemActivation.SECOND:
  773. this.ui.list.focus(QuickInputListFocus.Second);
  774. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  775. break;
  776. case ItemActivation.LAST:
  777. this.ui.list.focus(QuickInputListFocus.Last);
  778. this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
  779. break;
  780. default:
  781. this.trySelectFirst();
  782. break;
  783. }
  784. }
  785. if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
  786. if (this.canSelectMany) {
  787. this.ui.list.clearFocus();
  788. }
  789. else {
  790. this.trySelectFirst();
  791. }
  792. }
  793. if (this.activeItemsUpdated) {
  794. this.activeItemsUpdated = false;
  795. this.activeItemsToConfirm = this._activeItems;
  796. this.ui.list.setFocusedElements(this.activeItems);
  797. if (this.activeItemsToConfirm === this._activeItems) {
  798. this.activeItemsToConfirm = null;
  799. }
  800. }
  801. if (this.selectedItemsUpdated) {
  802. this.selectedItemsUpdated = false;
  803. this.selectedItemsToConfirm = this._selectedItems;
  804. if (this.canSelectMany) {
  805. this.ui.list.setCheckedElements(this.selectedItems);
  806. }
  807. else {
  808. this.ui.list.setSelectedElements(this.selectedItems);
  809. }
  810. if (this.selectedItemsToConfirm === this._selectedItems) {
  811. this.selectedItemsToConfirm = null;
  812. }
  813. }
  814. this.ui.customButton.label = this.customLabel || '';
  815. this.ui.customButton.element.title = this.customHover || '';
  816. this.ui.setComboboxAccessibility(true);
  817. if (!visibilities.inputBox) {
  818. // we need to move focus into the tree to detect keybindings
  819. // properly when the input box is not visible (quick nav)
  820. this.ui.list.domFocus();
  821. // Focus the first element in the list if multiselect is enabled
  822. if (this.canSelectMany) {
  823. this.ui.list.focus(QuickInputListFocus.First);
  824. }
  825. }
  826. // Set the scroll position to what it was before updating the items
  827. if (this.keepScrollPosition) {
  828. this.scrollTop = scrollTopBefore;
  829. }
  830. }
  831. }
  832. QuickPick.DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
  833. export class QuickInputController extends Disposable {
  834. constructor(options) {
  835. super();
  836. this.options = options;
  837. this.comboboxAccessibility = false;
  838. this.enabled = true;
  839. this.onDidAcceptEmitter = this._register(new Emitter());
  840. this.onDidCustomEmitter = this._register(new Emitter());
  841. this.onDidTriggerButtonEmitter = this._register(new Emitter());
  842. this.keyMods = { ctrlCmd: false, alt: false };
  843. this.controller = null;
  844. this.onShowEmitter = this._register(new Emitter());
  845. this.onShow = this.onShowEmitter.event;
  846. this.onHideEmitter = this._register(new Emitter());
  847. this.onHide = this.onHideEmitter.event;
  848. this.idPrefix = options.idPrefix;
  849. this.parentElement = options.container;
  850. this.styles = options.styles;
  851. this.registerKeyModsListeners();
  852. }
  853. registerKeyModsListeners() {
  854. const listener = (e) => {
  855. this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey;
  856. this.keyMods.alt = e.altKey;
  857. };
  858. this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, listener, true));
  859. this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, listener, true));
  860. this._register(dom.addDisposableListener(window, dom.EventType.MOUSE_DOWN, listener, true));
  861. }
  862. getUI() {
  863. if (this.ui) {
  864. return this.ui;
  865. }
  866. const container = dom.append(this.parentElement, $('.quick-input-widget.show-file-icons'));
  867. container.tabIndex = -1;
  868. container.style.display = 'none';
  869. const styleSheet = dom.createStyleSheet(container);
  870. const titleBar = dom.append(container, $('.quick-input-titlebar'));
  871. const leftActionBar = this._register(new ActionBar(titleBar));
  872. leftActionBar.domNode.classList.add('quick-input-left-action-bar');
  873. const title = dom.append(titleBar, $('.quick-input-title'));
  874. const rightActionBar = this._register(new ActionBar(titleBar));
  875. rightActionBar.domNode.classList.add('quick-input-right-action-bar');
  876. const description1 = dom.append(container, $('.quick-input-description'));
  877. const headerContainer = dom.append(container, $('.quick-input-header'));
  878. const checkAll = dom.append(headerContainer, $('input.quick-input-check-all'));
  879. checkAll.type = 'checkbox';
  880. checkAll.setAttribute('aria-label', localize('quickInput.checkAll', "Toggle all checkboxes"));
  881. this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => {
  882. const checked = checkAll.checked;
  883. list.setAllVisibleChecked(checked);
  884. }));
  885. this._register(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => {
  886. if (e.x || e.y) { // Avoid 'click' triggered by 'space'...
  887. inputBox.setFocus();
  888. }
  889. }));
  890. const description2 = dom.append(headerContainer, $('.quick-input-description'));
  891. const extraContainer = dom.append(headerContainer, $('.quick-input-and-message'));
  892. const filterContainer = dom.append(extraContainer, $('.quick-input-filter'));
  893. const inputBox = this._register(new QuickInputBox(filterContainer));
  894. inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`);
  895. const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count'));
  896. visibleCountContainer.setAttribute('aria-live', 'polite');
  897. visibleCountContainer.setAttribute('aria-atomic', 'true');
  898. 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") });
  899. const countContainer = dom.append(filterContainer, $('.quick-input-count'));
  900. countContainer.setAttribute('aria-live', 'polite');
  901. 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") });
  902. const okContainer = dom.append(headerContainer, $('.quick-input-action'));
  903. const ok = new Button(okContainer);
  904. ok.label = localize('ok', "OK");
  905. this._register(ok.onDidClick(e => {
  906. this.onDidAcceptEmitter.fire();
  907. }));
  908. const customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
  909. const customButton = new Button(customButtonContainer);
  910. customButton.label = localize('custom', "Custom");
  911. this._register(customButton.onDidClick(e => {
  912. this.onDidCustomEmitter.fire();
  913. }));
  914. const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`));
  915. const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options));
  916. this._register(list.onChangedAllVisibleChecked(checked => {
  917. checkAll.checked = checked;
  918. }));
  919. this._register(list.onChangedVisibleCount(c => {
  920. visibleCount.setCount(c);
  921. }));
  922. this._register(list.onChangedCheckedCount(c => {
  923. count.setCount(c);
  924. }));
  925. this._register(list.onLeave(() => {
  926. // Defer to avoid the input field reacting to the triggering key.
  927. setTimeout(() => {
  928. inputBox.setFocus();
  929. if (this.controller instanceof QuickPick && this.controller.canSelectMany) {
  930. list.clearFocus();
  931. }
  932. }, 0);
  933. }));
  934. this._register(list.onDidChangeFocus(() => {
  935. if (this.comboboxAccessibility) {
  936. this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || '');
  937. }
  938. }));
  939. const progressBar = new ProgressBar(container);
  940. progressBar.getContainer().classList.add('quick-input-progress');
  941. const focusTracker = dom.trackFocus(container);
  942. this._register(focusTracker);
  943. this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => {
  944. this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined;
  945. }, true));
  946. this._register(focusTracker.onDidBlur(() => {
  947. if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) {
  948. this.hide(QuickInputHideReason.Blur);
  949. }
  950. this.previousFocusElement = undefined;
  951. }));
  952. this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e) => {
  953. inputBox.setFocus();
  954. }));
  955. this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e) => {
  956. const event = new StandardKeyboardEvent(e);
  957. switch (event.keyCode) {
  958. case 3 /* KeyCode.Enter */:
  959. dom.EventHelper.stop(e, true);
  960. this.onDidAcceptEmitter.fire();
  961. break;
  962. case 9 /* KeyCode.Escape */:
  963. dom.EventHelper.stop(e, true);
  964. this.hide(QuickInputHideReason.Gesture);
  965. break;
  966. case 2 /* KeyCode.Tab */:
  967. if (!event.altKey && !event.ctrlKey && !event.metaKey) {
  968. const selectors = ['.action-label.codicon'];
  969. if (container.classList.contains('show-checkboxes')) {
  970. selectors.push('input');
  971. }
  972. else {
  973. selectors.push('input[type=text]');
  974. }
  975. if (this.getUI().list.isDisplayed()) {
  976. selectors.push('.monaco-list');
  977. }
  978. const stops = container.querySelectorAll(selectors.join(', '));
  979. if (event.shiftKey && event.target === stops[0]) {
  980. dom.EventHelper.stop(e, true);
  981. stops[stops.length - 1].focus();
  982. }
  983. else if (!event.shiftKey && event.target === stops[stops.length - 1]) {
  984. dom.EventHelper.stop(e, true);
  985. stops[0].focus();
  986. }
  987. }
  988. break;
  989. }
  990. }));
  991. this.ui = {
  992. container,
  993. styleSheet,
  994. leftActionBar,
  995. titleBar,
  996. title,
  997. description1,
  998. description2,
  999. rightActionBar,
  1000. checkAll,
  1001. filterContainer,
  1002. inputBox,
  1003. visibleCountContainer,
  1004. visibleCount,
  1005. countContainer,
  1006. count,
  1007. okContainer,
  1008. ok,
  1009. message,
  1010. customButtonContainer,
  1011. customButton,
  1012. list,
  1013. progressBar,
  1014. onDidAccept: this.onDidAcceptEmitter.event,
  1015. onDidCustom: this.onDidCustomEmitter.event,
  1016. onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
  1017. ignoreFocusOut: false,
  1018. keyMods: this.keyMods,
  1019. isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(),
  1020. show: controller => this.show(controller),
  1021. hide: () => this.hide(),
  1022. setVisibilities: visibilities => this.setVisibilities(visibilities),
  1023. setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled),
  1024. setEnabled: enabled => this.setEnabled(enabled),
  1025. setContextKey: contextKey => this.options.setContextKey(contextKey),
  1026. };
  1027. this.updateStyles();
  1028. return this.ui;
  1029. }
  1030. pick(picks, options = {}, token = CancellationToken.None) {
  1031. return new Promise((doResolve, reject) => {
  1032. let resolve = (result) => {
  1033. var _a;
  1034. resolve = doResolve;
  1035. (_a = options.onKeyMods) === null || _a === void 0 ? void 0 : _a.call(options, input.keyMods);
  1036. doResolve(result);
  1037. };
  1038. if (token.isCancellationRequested) {
  1039. resolve(undefined);
  1040. return;
  1041. }
  1042. const input = this.createQuickPick();
  1043. let activeItem;
  1044. const disposables = [
  1045. input,
  1046. input.onDidAccept(() => {
  1047. if (input.canSelectMany) {
  1048. resolve(input.selectedItems.slice());
  1049. input.hide();
  1050. }
  1051. else {
  1052. const result = input.activeItems[0];
  1053. if (result) {
  1054. resolve(result);
  1055. input.hide();
  1056. }
  1057. }
  1058. }),
  1059. input.onDidChangeActive(items => {
  1060. const focused = items[0];
  1061. if (focused && options.onDidFocus) {
  1062. options.onDidFocus(focused);
  1063. }
  1064. }),
  1065. input.onDidChangeSelection(items => {
  1066. if (!input.canSelectMany) {
  1067. const result = items[0];
  1068. if (result) {
  1069. resolve(result);
  1070. input.hide();
  1071. }
  1072. }
  1073. }),
  1074. input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton(Object.assign(Object.assign({}, event), { removeItem: () => {
  1075. const index = input.items.indexOf(event.item);
  1076. if (index !== -1) {
  1077. const items = input.items.slice();
  1078. const removed = items.splice(index, 1);
  1079. const activeItems = input.activeItems.filter(activeItem => activeItem !== removed[0]);
  1080. const keepScrollPositionBefore = input.keepScrollPosition;
  1081. input.keepScrollPosition = true;
  1082. input.items = items;
  1083. if (activeItems) {
  1084. input.activeItems = activeItems;
  1085. }
  1086. input.keepScrollPosition = keepScrollPositionBefore;
  1087. }
  1088. } }))),
  1089. input.onDidChangeValue(value => {
  1090. if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) {
  1091. input.activeItems = [activeItem];
  1092. }
  1093. }),
  1094. token.onCancellationRequested(() => {
  1095. input.hide();
  1096. }),
  1097. input.onDidHide(() => {
  1098. dispose(disposables);
  1099. resolve(undefined);
  1100. }),
  1101. ];
  1102. input.title = options.title;
  1103. input.canSelectMany = !!options.canPickMany;
  1104. input.placeholder = options.placeHolder;
  1105. input.ignoreFocusOut = !!options.ignoreFocusLost;
  1106. input.matchOnDescription = !!options.matchOnDescription;
  1107. input.matchOnDetail = !!options.matchOnDetail;
  1108. input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true
  1109. input.autoFocusOnList = (options.autoFocusOnList === undefined) || options.autoFocusOnList; // default to true
  1110. input.quickNavigate = options.quickNavigate;
  1111. input.hideInput = !!options.hideInput;
  1112. input.contextKey = options.contextKey;
  1113. input.busy = true;
  1114. Promise.all([picks, options.activeItem])
  1115. .then(([items, _activeItem]) => {
  1116. activeItem = _activeItem;
  1117. input.busy = false;
  1118. input.items = items;
  1119. if (input.canSelectMany) {
  1120. input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked);
  1121. }
  1122. if (activeItem) {
  1123. input.activeItems = [activeItem];
  1124. }
  1125. });
  1126. input.show();
  1127. Promise.resolve(picks).then(undefined, err => {
  1128. reject(err);
  1129. input.hide();
  1130. });
  1131. });
  1132. }
  1133. createQuickPick() {
  1134. const ui = this.getUI();
  1135. return new QuickPick(ui);
  1136. }
  1137. show(controller) {
  1138. const ui = this.getUI();
  1139. this.onShowEmitter.fire();
  1140. const oldController = this.controller;
  1141. this.controller = controller;
  1142. if (oldController) {
  1143. oldController.didHide();
  1144. }
  1145. this.setEnabled(true);
  1146. ui.leftActionBar.clear();
  1147. ui.title.textContent = '';
  1148. ui.description1.textContent = '';
  1149. ui.description2.textContent = '';
  1150. ui.rightActionBar.clear();
  1151. ui.checkAll.checked = false;
  1152. // ui.inputBox.value = ''; Avoid triggering an event.
  1153. ui.inputBox.placeholder = '';
  1154. ui.inputBox.password = false;
  1155. ui.inputBox.showDecoration(Severity.Ignore);
  1156. ui.visibleCount.setCount(0);
  1157. ui.count.setCount(0);
  1158. dom.reset(ui.message);
  1159. ui.progressBar.stop();
  1160. ui.list.setElements([]);
  1161. ui.list.matchOnDescription = false;
  1162. ui.list.matchOnDetail = false;
  1163. ui.list.matchOnLabel = true;
  1164. ui.list.sortByLabel = true;
  1165. ui.ignoreFocusOut = false;
  1166. this.setComboboxAccessibility(false);
  1167. ui.inputBox.ariaLabel = '';
  1168. const backKeybindingLabel = this.options.backKeybindingLabel();
  1169. backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back");
  1170. ui.container.style.display = '';
  1171. this.updateLayout();
  1172. ui.inputBox.setFocus();
  1173. }
  1174. setVisibilities(visibilities) {
  1175. const ui = this.getUI();
  1176. ui.title.style.display = visibilities.title ? '' : 'none';
  1177. ui.description1.style.display = visibilities.description && (visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
  1178. ui.description2.style.display = visibilities.description && !(visibilities.inputBox || visibilities.checkAll) ? '' : 'none';
  1179. ui.checkAll.style.display = visibilities.checkAll ? '' : 'none';
  1180. ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none';
  1181. ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none';
  1182. ui.countContainer.style.display = visibilities.count ? '' : 'none';
  1183. ui.okContainer.style.display = visibilities.ok ? '' : 'none';
  1184. ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none';
  1185. ui.message.style.display = visibilities.message ? '' : 'none';
  1186. ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none';
  1187. ui.list.display(!!visibilities.list);
  1188. ui.container.classList[visibilities.checkBox ? 'add' : 'remove']('show-checkboxes');
  1189. this.updateLayout(); // TODO
  1190. }
  1191. setComboboxAccessibility(enabled) {
  1192. if (enabled !== this.comboboxAccessibility) {
  1193. const ui = this.getUI();
  1194. this.comboboxAccessibility = enabled;
  1195. if (this.comboboxAccessibility) {
  1196. ui.inputBox.setAttribute('role', 'combobox');
  1197. ui.inputBox.setAttribute('aria-haspopup', 'true');
  1198. ui.inputBox.setAttribute('aria-autocomplete', 'list');
  1199. ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || '');
  1200. }
  1201. else {
  1202. ui.inputBox.removeAttribute('role');
  1203. ui.inputBox.removeAttribute('aria-haspopup');
  1204. ui.inputBox.removeAttribute('aria-autocomplete');
  1205. ui.inputBox.removeAttribute('aria-activedescendant');
  1206. }
  1207. }
  1208. }
  1209. setEnabled(enabled) {
  1210. if (enabled !== this.enabled) {
  1211. this.enabled = enabled;
  1212. for (const item of this.getUI().leftActionBar.viewItems) {
  1213. item.getAction().enabled = enabled;
  1214. }
  1215. for (const item of this.getUI().rightActionBar.viewItems) {
  1216. item.getAction().enabled = enabled;
  1217. }
  1218. this.getUI().checkAll.disabled = !enabled;
  1219. // this.getUI().inputBox.enabled = enabled; Avoid loosing focus.
  1220. this.getUI().ok.enabled = enabled;
  1221. this.getUI().list.enabled = enabled;
  1222. }
  1223. }
  1224. hide(reason) {
  1225. var _a;
  1226. const controller = this.controller;
  1227. if (controller) {
  1228. const focusChanged = !((_a = this.ui) === null || _a === void 0 ? void 0 : _a.container.contains(document.activeElement));
  1229. this.controller = null;
  1230. this.onHideEmitter.fire();
  1231. this.getUI().container.style.display = 'none';
  1232. if (!focusChanged) {
  1233. let currentElement = this.previousFocusElement;
  1234. while (currentElement && !currentElement.offsetParent) {
  1235. currentElement = withNullAsUndefined(currentElement.parentElement);
  1236. }
  1237. if (currentElement === null || currentElement === void 0 ? void 0 : currentElement.offsetParent) {
  1238. currentElement.focus();
  1239. this.previousFocusElement = undefined;
  1240. }
  1241. else {
  1242. this.options.returnFocus();
  1243. }
  1244. }
  1245. controller.didHide(reason);
  1246. }
  1247. }
  1248. layout(dimension, titleBarOffset) {
  1249. this.dimension = dimension;
  1250. this.titleBarOffset = titleBarOffset;
  1251. this.updateLayout();
  1252. }
  1253. updateLayout() {
  1254. if (this.ui) {
  1255. this.ui.container.style.top = `${this.titleBarOffset}px`;
  1256. const style = this.ui.container.style;
  1257. const width = Math.min(this.dimension.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH);
  1258. style.width = width + 'px';
  1259. style.marginLeft = '-' + (width / 2) + 'px';
  1260. this.ui.inputBox.layout();
  1261. this.ui.list.layout(this.dimension && this.dimension.height * 0.4);
  1262. }
  1263. }
  1264. applyStyles(styles) {
  1265. this.styles = styles;
  1266. this.updateStyles();
  1267. }
  1268. updateStyles() {
  1269. if (this.ui) {
  1270. const { quickInputTitleBackground, quickInputBackground, quickInputForeground, contrastBorder, widgetShadow, } = this.styles.widget;
  1271. this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ? quickInputTitleBackground.toString() : '';
  1272. this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : '';
  1273. this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : '';
  1274. this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : '';
  1275. this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
  1276. this.ui.inputBox.style(this.styles.inputBox);
  1277. this.ui.count.style(this.styles.countBadge);
  1278. this.ui.ok.style(this.styles.button);
  1279. this.ui.customButton.style(this.styles.button);
  1280. this.ui.progressBar.style(this.styles.progressBar);
  1281. this.ui.list.style(this.styles.list);
  1282. const content = [];
  1283. if (this.styles.list.pickerGroupBorder) {
  1284. content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.list.pickerGroupBorder}; }`);
  1285. }
  1286. if (this.styles.list.pickerGroupForeground) {
  1287. content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`);
  1288. }
  1289. if (this.styles.keybindingLabel.keybindingLabelBackground ||
  1290. this.styles.keybindingLabel.keybindingLabelBorder ||
  1291. this.styles.keybindingLabel.keybindingLabelBottomBorder ||
  1292. this.styles.keybindingLabel.keybindingLabelShadow ||
  1293. this.styles.keybindingLabel.keybindingLabelForeground) {
  1294. content.push('.quick-input-list .monaco-keybinding > .monaco-keybinding-key {');
  1295. if (this.styles.keybindingLabel.keybindingLabelBackground) {
  1296. content.push(`background-color: ${this.styles.keybindingLabel.keybindingLabelBackground};`);
  1297. }
  1298. if (this.styles.keybindingLabel.keybindingLabelBorder) {
  1299. // Order matters here. `border-color` must come before `border-bottom-color`.
  1300. content.push(`border-color: ${this.styles.keybindingLabel.keybindingLabelBorder};`);
  1301. }
  1302. if (this.styles.keybindingLabel.keybindingLabelBottomBorder) {
  1303. content.push(`border-bottom-color: ${this.styles.keybindingLabel.keybindingLabelBottomBorder};`);
  1304. }
  1305. if (this.styles.keybindingLabel.keybindingLabelShadow) {
  1306. content.push(`box-shadow: inset 0 -1px 0 ${this.styles.keybindingLabel.keybindingLabelShadow};`);
  1307. }
  1308. if (this.styles.keybindingLabel.keybindingLabelForeground) {
  1309. content.push(`color: ${this.styles.keybindingLabel.keybindingLabelForeground};`);
  1310. }
  1311. content.push('}');
  1312. }
  1313. const newStyles = content.join('\n');
  1314. if (newStyles !== this.ui.styleSheet.textContent) {
  1315. this.ui.styleSheet.textContent = newStyles;
  1316. }
  1317. }
  1318. }
  1319. }
  1320. QuickInputController.MAX_WIDTH = 600; // Max total width of quick input widget