6e3c2d07c4258a10514ae57fd75e925873580da50b693d69e077f91365a949eb4cd9fd11f438e3273fb2af1295562e76e75762d8bda44401dd666b2a2c9fe3 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  12. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  13. return new (P || (P = Promise))(function (resolve, reject) {
  14. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  15. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  16. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  17. step((generator = generator.apply(thisArg, _arguments || [])).next());
  18. });
  19. };
  20. import * as dom from '../../../browser/dom.js';
  21. import { StandardKeyboardEvent } from '../../../browser/keyboardEvent.js';
  22. import { ActionBar } from '../../../browser/ui/actionbar/actionbar.js';
  23. import { IconLabel } from '../../../browser/ui/iconLabel/iconLabel.js';
  24. import { KeybindingLabel } from '../../../browser/ui/keybindingLabel/keybindingLabel.js';
  25. import { Action } from '../../../common/actions.js';
  26. import { range } from '../../../common/arrays.js';
  27. import { getCodiconAriaLabel } from '../../../common/codicons.js';
  28. import { compareAnything } from '../../../common/comparers.js';
  29. import { memoize } from '../../../common/decorators.js';
  30. import { Emitter, Event } from '../../../common/event.js';
  31. import { matchesFuzzyIconAware, parseLabelWithIcons } from '../../../common/iconLabels.js';
  32. import { dispose } from '../../../common/lifecycle.js';
  33. import * as platform from '../../../common/platform.js';
  34. import { ltrim } from '../../../common/strings.js';
  35. import { withNullAsUndefined } from '../../../common/types.js';
  36. import { getIconClass } from './quickInputUtils.js';
  37. import './media/quickInput.css';
  38. import { localize } from '../../../../nls.js';
  39. const $ = dom.$;
  40. class ListElement {
  41. constructor(init) {
  42. this.hidden = false;
  43. this._onChecked = new Emitter();
  44. this.onChecked = this._onChecked.event;
  45. Object.assign(this, init);
  46. }
  47. get checked() {
  48. return !!this._checked;
  49. }
  50. set checked(value) {
  51. if (value !== this._checked) {
  52. this._checked = value;
  53. this._onChecked.fire(value);
  54. }
  55. }
  56. dispose() {
  57. this._onChecked.dispose();
  58. }
  59. }
  60. class ListElementRenderer {
  61. get templateId() {
  62. return ListElementRenderer.ID;
  63. }
  64. renderTemplate(container) {
  65. const data = Object.create(null);
  66. data.toDisposeElement = [];
  67. data.toDisposeTemplate = [];
  68. data.entry = dom.append(container, $('.quick-input-list-entry'));
  69. // Checkbox
  70. const label = dom.append(data.entry, $('label.quick-input-list-label'));
  71. data.toDisposeTemplate.push(dom.addStandardDisposableListener(label, dom.EventType.CLICK, e => {
  72. if (!data.checkbox.offsetParent) { // If checkbox not visible:
  73. e.preventDefault(); // Prevent toggle of checkbox when it is immediately shown afterwards. #91740
  74. }
  75. }));
  76. data.checkbox = dom.append(label, $('input.quick-input-list-checkbox'));
  77. data.checkbox.type = 'checkbox';
  78. data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
  79. data.element.checked = data.checkbox.checked;
  80. }));
  81. // Rows
  82. const rows = dom.append(label, $('.quick-input-list-rows'));
  83. const row1 = dom.append(rows, $('.quick-input-list-row'));
  84. const row2 = dom.append(rows, $('.quick-input-list-row'));
  85. // Label
  86. data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportIcons: true });
  87. // Keybinding
  88. const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
  89. data.keybinding = new KeybindingLabel(keybindingContainer, platform.OS);
  90. // Detail
  91. const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
  92. data.detail = new IconLabel(detailContainer, { supportHighlights: true, supportIcons: true });
  93. // Separator
  94. data.separator = dom.append(data.entry, $('.quick-input-list-separator'));
  95. // Actions
  96. data.actionBar = new ActionBar(data.entry);
  97. data.actionBar.domNode.classList.add('quick-input-list-entry-action-bar');
  98. data.toDisposeTemplate.push(data.actionBar);
  99. return data;
  100. }
  101. renderElement(element, index, data) {
  102. data.toDisposeElement = dispose(data.toDisposeElement);
  103. data.element = element;
  104. data.checkbox.checked = element.checked;
  105. data.toDisposeElement.push(element.onChecked(checked => data.checkbox.checked = checked));
  106. const { labelHighlights, descriptionHighlights, detailHighlights } = element;
  107. // Label
  108. const options = Object.create(null);
  109. options.matches = labelHighlights || [];
  110. options.descriptionTitle = element.saneDescription;
  111. options.descriptionMatches = descriptionHighlights || [];
  112. options.extraClasses = element.item.iconClasses;
  113. options.italic = element.item.italic;
  114. options.strikethrough = element.item.strikethrough;
  115. data.label.setLabel(element.saneLabel, element.saneDescription, options);
  116. // Keybinding
  117. data.keybinding.set(element.item.keybinding);
  118. // Meta
  119. if (element.saneDetail) {
  120. data.detail.setLabel(element.saneDetail, undefined, {
  121. matches: detailHighlights,
  122. title: element.saneDetail
  123. });
  124. }
  125. // Separator
  126. if (element.separator && element.separator.label) {
  127. data.separator.textContent = element.separator.label;
  128. data.separator.style.display = '';
  129. }
  130. else {
  131. data.separator.style.display = 'none';
  132. }
  133. data.entry.classList.toggle('quick-input-list-separator-border', !!element.separator);
  134. // Actions
  135. data.actionBar.clear();
  136. const buttons = element.item.buttons;
  137. if (buttons && buttons.length) {
  138. data.actionBar.push(buttons.map((button, index) => {
  139. let cssClasses = button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined);
  140. if (button.alwaysVisible) {
  141. cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible';
  142. }
  143. const action = new Action(`id-${index}`, '', cssClasses, true, () => __awaiter(this, void 0, void 0, function* () {
  144. element.fireButtonTriggered({
  145. button,
  146. item: element.item
  147. });
  148. }));
  149. action.tooltip = button.tooltip || '';
  150. return action;
  151. }), { icon: true, label: false });
  152. data.entry.classList.add('has-actions');
  153. }
  154. else {
  155. data.entry.classList.remove('has-actions');
  156. }
  157. }
  158. disposeElement(element, index, data) {
  159. data.toDisposeElement = dispose(data.toDisposeElement);
  160. }
  161. disposeTemplate(data) {
  162. data.toDisposeElement = dispose(data.toDisposeElement);
  163. data.toDisposeTemplate = dispose(data.toDisposeTemplate);
  164. }
  165. }
  166. ListElementRenderer.ID = 'listelement';
  167. class ListElementDelegate {
  168. getHeight(element) {
  169. return element.saneDetail ? 44 : 22;
  170. }
  171. getTemplateId(element) {
  172. return ListElementRenderer.ID;
  173. }
  174. }
  175. export var QuickInputListFocus;
  176. (function (QuickInputListFocus) {
  177. QuickInputListFocus[QuickInputListFocus["First"] = 1] = "First";
  178. QuickInputListFocus[QuickInputListFocus["Second"] = 2] = "Second";
  179. QuickInputListFocus[QuickInputListFocus["Last"] = 3] = "Last";
  180. QuickInputListFocus[QuickInputListFocus["Next"] = 4] = "Next";
  181. QuickInputListFocus[QuickInputListFocus["Previous"] = 5] = "Previous";
  182. QuickInputListFocus[QuickInputListFocus["NextPage"] = 6] = "NextPage";
  183. QuickInputListFocus[QuickInputListFocus["PreviousPage"] = 7] = "PreviousPage";
  184. })(QuickInputListFocus || (QuickInputListFocus = {}));
  185. export class QuickInputList {
  186. constructor(parent, id, options) {
  187. this.parent = parent;
  188. this.inputElements = [];
  189. this.elements = [];
  190. this.elementsToIndexes = new Map();
  191. this.matchOnDescription = false;
  192. this.matchOnDetail = false;
  193. this.matchOnLabel = true;
  194. this.matchOnLabelMode = 'fuzzy';
  195. this.matchOnMeta = true;
  196. this.sortByLabel = true;
  197. this._onChangedAllVisibleChecked = new Emitter();
  198. this.onChangedAllVisibleChecked = this._onChangedAllVisibleChecked.event;
  199. this._onChangedCheckedCount = new Emitter();
  200. this.onChangedCheckedCount = this._onChangedCheckedCount.event;
  201. this._onChangedVisibleCount = new Emitter();
  202. this.onChangedVisibleCount = this._onChangedVisibleCount.event;
  203. this._onChangedCheckedElements = new Emitter();
  204. this.onChangedCheckedElements = this._onChangedCheckedElements.event;
  205. this._onButtonTriggered = new Emitter();
  206. this.onButtonTriggered = this._onButtonTriggered.event;
  207. this._onKeyDown = new Emitter();
  208. this.onKeyDown = this._onKeyDown.event;
  209. this._onLeave = new Emitter();
  210. this.onLeave = this._onLeave.event;
  211. this._fireCheckedEvents = true;
  212. this.elementDisposables = [];
  213. this.disposables = [];
  214. this.id = id;
  215. this.container = dom.append(this.parent, $('.quick-input-list'));
  216. const delegate = new ListElementDelegate();
  217. const accessibilityProvider = new QuickInputAccessibilityProvider();
  218. this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], {
  219. identityProvider: { getId: element => element.saneLabel },
  220. setRowLineHeight: false,
  221. multipleSelectionSupport: false,
  222. horizontalScrolling: false,
  223. accessibilityProvider
  224. });
  225. this.list.getHTMLElement().id = id;
  226. this.disposables.push(this.list);
  227. this.disposables.push(this.list.onKeyDown(e => {
  228. const event = new StandardKeyboardEvent(e);
  229. switch (event.keyCode) {
  230. case 10 /* KeyCode.Space */:
  231. this.toggleCheckbox();
  232. break;
  233. case 31 /* KeyCode.KeyA */:
  234. if (platform.isMacintosh ? e.metaKey : e.ctrlKey) {
  235. this.list.setFocus(range(this.list.length));
  236. }
  237. break;
  238. case 16 /* KeyCode.UpArrow */: {
  239. const focus1 = this.list.getFocus();
  240. if (focus1.length === 1 && focus1[0] === 0) {
  241. this._onLeave.fire();
  242. }
  243. break;
  244. }
  245. case 18 /* KeyCode.DownArrow */: {
  246. const focus2 = this.list.getFocus();
  247. if (focus2.length === 1 && focus2[0] === this.list.length - 1) {
  248. this._onLeave.fire();
  249. }
  250. break;
  251. }
  252. }
  253. this._onKeyDown.fire(event);
  254. }));
  255. this.disposables.push(this.list.onMouseDown(e => {
  256. if (e.browserEvent.button !== 2) {
  257. // Works around / fixes #64350.
  258. e.browserEvent.preventDefault();
  259. }
  260. }));
  261. this.disposables.push(dom.addDisposableListener(this.container, dom.EventType.CLICK, e => {
  262. if (e.x || e.y) { // Avoid 'click' triggered by 'space' on checkbox.
  263. this._onLeave.fire();
  264. }
  265. }));
  266. this.disposables.push(this.list.onMouseMiddleClick(e => {
  267. this._onLeave.fire();
  268. }));
  269. this.disposables.push(this.list.onContextMenu(e => {
  270. if (typeof e.index === 'number') {
  271. e.browserEvent.preventDefault();
  272. // we want to treat a context menu event as
  273. // a gesture to open the item at the index
  274. // since we do not have any context menu
  275. // this enables for example macOS to Ctrl-
  276. // click on an item to open it.
  277. this.list.setSelection([e.index]);
  278. }
  279. }));
  280. this.disposables.push(this._onChangedAllVisibleChecked, this._onChangedCheckedCount, this._onChangedVisibleCount, this._onChangedCheckedElements, this._onButtonTriggered, this._onLeave, this._onKeyDown);
  281. }
  282. get onDidChangeFocus() {
  283. return Event.map(this.list.onDidChangeFocus, e => e.elements.map(e => e.item));
  284. }
  285. get onDidChangeSelection() {
  286. return Event.map(this.list.onDidChangeSelection, e => ({ items: e.elements.map(e => e.item), event: e.browserEvent }));
  287. }
  288. get scrollTop() {
  289. return this.list.scrollTop;
  290. }
  291. set scrollTop(scrollTop) {
  292. this.list.scrollTop = scrollTop;
  293. }
  294. getAllVisibleChecked() {
  295. return this.allVisibleChecked(this.elements, false);
  296. }
  297. allVisibleChecked(elements, whenNoneVisible = true) {
  298. for (let i = 0, n = elements.length; i < n; i++) {
  299. const element = elements[i];
  300. if (!element.hidden) {
  301. if (!element.checked) {
  302. return false;
  303. }
  304. else {
  305. whenNoneVisible = true;
  306. }
  307. }
  308. }
  309. return whenNoneVisible;
  310. }
  311. getCheckedCount() {
  312. let count = 0;
  313. const elements = this.elements;
  314. for (let i = 0, n = elements.length; i < n; i++) {
  315. if (elements[i].checked) {
  316. count++;
  317. }
  318. }
  319. return count;
  320. }
  321. getVisibleCount() {
  322. let count = 0;
  323. const elements = this.elements;
  324. for (let i = 0, n = elements.length; i < n; i++) {
  325. if (!elements[i].hidden) {
  326. count++;
  327. }
  328. }
  329. return count;
  330. }
  331. setAllVisibleChecked(checked) {
  332. try {
  333. this._fireCheckedEvents = false;
  334. this.elements.forEach(element => {
  335. if (!element.hidden) {
  336. element.checked = checked;
  337. }
  338. });
  339. }
  340. finally {
  341. this._fireCheckedEvents = true;
  342. this.fireCheckedEvents();
  343. }
  344. }
  345. setElements(inputElements) {
  346. this.elementDisposables = dispose(this.elementDisposables);
  347. const fireButtonTriggered = (event) => this.fireButtonTriggered(event);
  348. this.inputElements = inputElements;
  349. this.elements = inputElements.reduce((result, item, index) => {
  350. var _a, _b, _c;
  351. if (item.type !== 'separator') {
  352. const previous = index && inputElements[index - 1];
  353. const saneLabel = item.label && item.label.replace(/\r?\n/g, ' ');
  354. const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim();
  355. const saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' ');
  356. const saneDescription = item.description && item.description.replace(/\r?\n/g, ' ');
  357. const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' ');
  358. const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail]
  359. .map(s => getCodiconAriaLabel(s))
  360. .filter(s => !!s)
  361. .join(', ');
  362. const hasCheckbox = this.parent.classList.contains('show-checkboxes');
  363. result.push(new ListElement({
  364. hasCheckbox,
  365. index,
  366. item,
  367. saneLabel,
  368. saneSortLabel,
  369. saneMeta,
  370. saneAriaLabel,
  371. saneDescription,
  372. saneDetail,
  373. labelHighlights: (_a = item.highlights) === null || _a === void 0 ? void 0 : _a.label,
  374. descriptionHighlights: (_b = item.highlights) === null || _b === void 0 ? void 0 : _b.description,
  375. detailHighlights: (_c = item.highlights) === null || _c === void 0 ? void 0 : _c.detail,
  376. checked: false,
  377. separator: previous && previous.type === 'separator' ? previous : undefined,
  378. fireButtonTriggered
  379. }));
  380. }
  381. return result;
  382. }, []);
  383. this.elementDisposables.push(...this.elements);
  384. this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
  385. this.elementsToIndexes = this.elements.reduce((map, element, index) => {
  386. map.set(element.item, index);
  387. return map;
  388. }, new Map());
  389. this.list.splice(0, this.list.length); // Clear focus and selection first, sending the events when the list is empty.
  390. this.list.splice(0, this.list.length, this.elements);
  391. this._onChangedVisibleCount.fire(this.elements.length);
  392. }
  393. getFocusedElements() {
  394. return this.list.getFocusedElements()
  395. .map(e => e.item);
  396. }
  397. setFocusedElements(items) {
  398. this.list.setFocus(items
  399. .filter(item => this.elementsToIndexes.has(item))
  400. .map(item => this.elementsToIndexes.get(item)));
  401. if (items.length > 0) {
  402. const focused = this.list.getFocus()[0];
  403. if (typeof focused === 'number') {
  404. this.list.reveal(focused);
  405. }
  406. }
  407. }
  408. getActiveDescendant() {
  409. return this.list.getHTMLElement().getAttribute('aria-activedescendant');
  410. }
  411. setSelectedElements(items) {
  412. this.list.setSelection(items
  413. .filter(item => this.elementsToIndexes.has(item))
  414. .map(item => this.elementsToIndexes.get(item)));
  415. }
  416. getCheckedElements() {
  417. return this.elements.filter(e => e.checked)
  418. .map(e => e.item);
  419. }
  420. setCheckedElements(items) {
  421. try {
  422. this._fireCheckedEvents = false;
  423. const checked = new Set();
  424. for (const item of items) {
  425. checked.add(item);
  426. }
  427. for (const element of this.elements) {
  428. element.checked = checked.has(element.item);
  429. }
  430. }
  431. finally {
  432. this._fireCheckedEvents = true;
  433. this.fireCheckedEvents();
  434. }
  435. }
  436. set enabled(value) {
  437. this.list.getHTMLElement().style.pointerEvents = value ? '' : 'none';
  438. }
  439. focus(what) {
  440. if (!this.list.length) {
  441. return;
  442. }
  443. if (what === QuickInputListFocus.Next && this.list.getFocus()[0] === this.list.length - 1) {
  444. what = QuickInputListFocus.First;
  445. }
  446. if (what === QuickInputListFocus.Previous && this.list.getFocus()[0] === 0) {
  447. what = QuickInputListFocus.Last;
  448. }
  449. if (what === QuickInputListFocus.Second && this.list.length < 2) {
  450. what = QuickInputListFocus.First;
  451. }
  452. switch (what) {
  453. case QuickInputListFocus.First:
  454. this.list.focusFirst();
  455. break;
  456. case QuickInputListFocus.Second:
  457. this.list.focusNth(1);
  458. break;
  459. case QuickInputListFocus.Last:
  460. this.list.focusLast();
  461. break;
  462. case QuickInputListFocus.Next:
  463. this.list.focusNext();
  464. break;
  465. case QuickInputListFocus.Previous:
  466. this.list.focusPrevious();
  467. break;
  468. case QuickInputListFocus.NextPage:
  469. this.list.focusNextPage();
  470. break;
  471. case QuickInputListFocus.PreviousPage:
  472. this.list.focusPreviousPage();
  473. break;
  474. }
  475. const focused = this.list.getFocus()[0];
  476. if (typeof focused === 'number') {
  477. this.list.reveal(focused);
  478. }
  479. }
  480. clearFocus() {
  481. this.list.setFocus([]);
  482. }
  483. domFocus() {
  484. this.list.domFocus();
  485. }
  486. layout(maxHeight) {
  487. this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : '';
  488. this.list.layout();
  489. }
  490. filter(query) {
  491. if (!(this.sortByLabel || this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
  492. this.list.layout();
  493. return false;
  494. }
  495. const queryWithWhitespace = query;
  496. query = query.trim();
  497. // Reset filtering
  498. if (!query || !(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
  499. this.elements.forEach(element => {
  500. element.labelHighlights = undefined;
  501. element.descriptionHighlights = undefined;
  502. element.detailHighlights = undefined;
  503. element.hidden = false;
  504. const previous = element.index && this.inputElements[element.index - 1];
  505. element.separator = previous && previous.type === 'separator' ? previous : undefined;
  506. });
  507. }
  508. // Filter by value (since we support icons in labels, use $(..) aware fuzzy matching)
  509. else {
  510. let currentSeparator;
  511. this.elements.forEach(element => {
  512. let labelHighlights;
  513. if (this.matchOnLabelMode === 'fuzzy') {
  514. labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
  515. }
  516. else {
  517. labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined;
  518. }
  519. const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
  520. const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
  521. const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
  522. if (labelHighlights || descriptionHighlights || detailHighlights || metaHighlights) {
  523. element.labelHighlights = labelHighlights;
  524. element.descriptionHighlights = descriptionHighlights;
  525. element.detailHighlights = detailHighlights;
  526. element.hidden = false;
  527. }
  528. else {
  529. element.labelHighlights = undefined;
  530. element.descriptionHighlights = undefined;
  531. element.detailHighlights = undefined;
  532. element.hidden = !element.item.alwaysShow;
  533. }
  534. element.separator = undefined;
  535. // we can show the separator unless the list gets sorted by match
  536. if (!this.sortByLabel) {
  537. const previous = element.index && this.inputElements[element.index - 1];
  538. currentSeparator = previous && previous.type === 'separator' ? previous : currentSeparator;
  539. if (currentSeparator && !element.hidden) {
  540. element.separator = currentSeparator;
  541. currentSeparator = undefined;
  542. }
  543. }
  544. });
  545. }
  546. const shownElements = this.elements.filter(element => !element.hidden);
  547. // Sort by value
  548. if (this.sortByLabel && query) {
  549. const normalizedSearchValue = query.toLowerCase();
  550. shownElements.sort((a, b) => {
  551. return compareEntries(a, b, normalizedSearchValue);
  552. });
  553. }
  554. this.elementsToIndexes = shownElements.reduce((map, element, index) => {
  555. map.set(element.item, index);
  556. return map;
  557. }, new Map());
  558. this.list.splice(0, this.list.length, shownElements);
  559. this.list.setFocus([]);
  560. this.list.layout();
  561. this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
  562. this._onChangedVisibleCount.fire(shownElements.length);
  563. return true;
  564. }
  565. toggleCheckbox() {
  566. try {
  567. this._fireCheckedEvents = false;
  568. const elements = this.list.getFocusedElements();
  569. const allChecked = this.allVisibleChecked(elements);
  570. for (const element of elements) {
  571. element.checked = !allChecked;
  572. }
  573. }
  574. finally {
  575. this._fireCheckedEvents = true;
  576. this.fireCheckedEvents();
  577. }
  578. }
  579. display(display) {
  580. this.container.style.display = display ? '' : 'none';
  581. }
  582. isDisplayed() {
  583. return this.container.style.display !== 'none';
  584. }
  585. dispose() {
  586. this.elementDisposables = dispose(this.elementDisposables);
  587. this.disposables = dispose(this.disposables);
  588. }
  589. fireCheckedEvents() {
  590. if (this._fireCheckedEvents) {
  591. this._onChangedAllVisibleChecked.fire(this.getAllVisibleChecked());
  592. this._onChangedCheckedCount.fire(this.getCheckedCount());
  593. this._onChangedCheckedElements.fire(this.getCheckedElements());
  594. }
  595. }
  596. fireButtonTriggered(event) {
  597. this._onButtonTriggered.fire(event);
  598. }
  599. style(styles) {
  600. this.list.style(styles);
  601. }
  602. }
  603. __decorate([
  604. memoize
  605. ], QuickInputList.prototype, "onDidChangeFocus", null);
  606. __decorate([
  607. memoize
  608. ], QuickInputList.prototype, "onDidChangeSelection", null);
  609. export function matchesContiguousIconAware(query, target) {
  610. const { text, iconOffsets } = target;
  611. // Return early if there are no icon markers in the word to match against
  612. if (!iconOffsets || iconOffsets.length === 0) {
  613. return matchesContiguous(query, text);
  614. }
  615. // Trim the word to match against because it could have leading
  616. // whitespace now if the word started with an icon
  617. const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
  618. const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
  619. // match on value without icon
  620. const matches = matchesContiguous(query, wordToMatchAgainstWithoutIconsTrimmed);
  621. // Map matches back to offsets with icon and trimming
  622. if (matches) {
  623. for (const match of matches) {
  624. const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
  625. match.start += iconOffset;
  626. match.end += iconOffset;
  627. }
  628. }
  629. return matches;
  630. }
  631. function matchesContiguous(word, wordToMatchAgainst) {
  632. const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
  633. if (matchIndex !== -1) {
  634. return [{ start: matchIndex, end: matchIndex + word.length }];
  635. }
  636. return null;
  637. }
  638. function compareEntries(elementA, elementB, lookFor) {
  639. const labelHighlightsA = elementA.labelHighlights || [];
  640. const labelHighlightsB = elementB.labelHighlights || [];
  641. if (labelHighlightsA.length && !labelHighlightsB.length) {
  642. return -1;
  643. }
  644. if (!labelHighlightsA.length && labelHighlightsB.length) {
  645. return 1;
  646. }
  647. if (labelHighlightsA.length === 0 && labelHighlightsB.length === 0) {
  648. return 0;
  649. }
  650. return compareAnything(elementA.saneSortLabel, elementB.saneSortLabel, lookFor);
  651. }
  652. class QuickInputAccessibilityProvider {
  653. getWidgetAriaLabel() {
  654. return localize('quickInput', "Quick Input");
  655. }
  656. getAriaLabel(element) {
  657. var _a;
  658. return ((_a = element.separator) === null || _a === void 0 ? void 0 : _a.label)
  659. ? `${element.saneAriaLabel}, ${element.separator.label}`
  660. : element.saneAriaLabel;
  661. }
  662. getWidgetRole() {
  663. return 'listbox';
  664. }
  665. getRole(element) {
  666. return element.hasCheckbox ? 'checkbox' : 'option';
  667. }
  668. isChecked(element) {
  669. if (!element.hasCheckbox) {
  670. return undefined;
  671. }
  672. return {
  673. value: element.checked,
  674. onDidChange: element.onChecked
  675. };
  676. }
  677. }