pickerQuickAccess.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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 { timeout } from '../../../base/common/async.js';
  15. import { CancellationTokenSource } from '../../../base/common/cancellation.js';
  16. import { Disposable, DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
  17. export var TriggerAction;
  18. (function (TriggerAction) {
  19. /**
  20. * Do nothing after the button was clicked.
  21. */
  22. TriggerAction[TriggerAction["NO_ACTION"] = 0] = "NO_ACTION";
  23. /**
  24. * Close the picker.
  25. */
  26. TriggerAction[TriggerAction["CLOSE_PICKER"] = 1] = "CLOSE_PICKER";
  27. /**
  28. * Update the results of the picker.
  29. */
  30. TriggerAction[TriggerAction["REFRESH_PICKER"] = 2] = "REFRESH_PICKER";
  31. /**
  32. * Remove the item from the picker.
  33. */
  34. TriggerAction[TriggerAction["REMOVE_ITEM"] = 3] = "REMOVE_ITEM";
  35. })(TriggerAction || (TriggerAction = {}));
  36. function isPicksWithActive(obj) {
  37. const candidate = obj;
  38. return Array.isArray(candidate.items);
  39. }
  40. function isFastAndSlowPicks(obj) {
  41. const candidate = obj;
  42. return !!candidate.picks && candidate.additionalPicks instanceof Promise;
  43. }
  44. export class PickerQuickAccessProvider extends Disposable {
  45. constructor(prefix, options) {
  46. super();
  47. this.prefix = prefix;
  48. this.options = options;
  49. }
  50. provide(picker, token) {
  51. var _a;
  52. const disposables = new DisposableStore();
  53. // Apply options if any
  54. picker.canAcceptInBackground = !!((_a = this.options) === null || _a === void 0 ? void 0 : _a.canAcceptInBackground);
  55. // Disable filtering & sorting, we control the results
  56. picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
  57. // Set initial picks and update on type
  58. let picksCts = undefined;
  59. const picksDisposable = disposables.add(new MutableDisposable());
  60. const updatePickerItems = () => __awaiter(this, void 0, void 0, function* () {
  61. const picksDisposables = picksDisposable.value = new DisposableStore();
  62. // Cancel any previous ask for picks and busy
  63. picksCts === null || picksCts === void 0 ? void 0 : picksCts.dispose(true);
  64. picker.busy = false;
  65. // Create new cancellation source for this run
  66. picksCts = new CancellationTokenSource(token);
  67. // Collect picks and support both long running and short or combined
  68. const picksToken = picksCts.token;
  69. const picksFilter = picker.value.substr(this.prefix.length).trim();
  70. const providedPicks = this._getPicks(picksFilter, picksDisposables, picksToken);
  71. const applyPicks = (picks, skipEmpty) => {
  72. var _a;
  73. let items;
  74. let activeItem = undefined;
  75. if (isPicksWithActive(picks)) {
  76. items = picks.items;
  77. activeItem = picks.active;
  78. }
  79. else {
  80. items = picks;
  81. }
  82. if (items.length === 0) {
  83. if (skipEmpty) {
  84. return false;
  85. }
  86. if (picksFilter.length > 0 && ((_a = this.options) === null || _a === void 0 ? void 0 : _a.noResultsPick)) {
  87. items = [this.options.noResultsPick];
  88. }
  89. }
  90. picker.items = items;
  91. if (activeItem) {
  92. picker.activeItems = [activeItem];
  93. }
  94. return true;
  95. };
  96. // No Picks
  97. if (providedPicks === null) {
  98. // Ignore
  99. }
  100. // Fast and Slow Picks
  101. else if (isFastAndSlowPicks(providedPicks)) {
  102. let fastPicksApplied = false;
  103. let slowPicksApplied = false;
  104. yield Promise.all([
  105. // Fast Picks: to reduce amount of flicker, we race against
  106. // the slow picks over 500ms and then set the fast picks.
  107. // If the slow picks are faster, we reduce the flicker by
  108. // only setting the items once.
  109. (() => __awaiter(this, void 0, void 0, function* () {
  110. yield timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY);
  111. if (picksToken.isCancellationRequested) {
  112. return;
  113. }
  114. if (!slowPicksApplied) {
  115. fastPicksApplied = applyPicks(providedPicks.picks, true /* skip over empty to reduce flicker */);
  116. }
  117. }))(),
  118. // Slow Picks: we await the slow picks and then set them at
  119. // once together with the fast picks, but only if we actually
  120. // have additional results.
  121. (() => __awaiter(this, void 0, void 0, function* () {
  122. picker.busy = true;
  123. try {
  124. const awaitedAdditionalPicks = yield providedPicks.additionalPicks;
  125. if (picksToken.isCancellationRequested) {
  126. return;
  127. }
  128. let picks;
  129. let activePick = undefined;
  130. if (isPicksWithActive(providedPicks.picks)) {
  131. picks = providedPicks.picks.items;
  132. activePick = providedPicks.picks.active;
  133. }
  134. else {
  135. picks = providedPicks.picks;
  136. }
  137. let additionalPicks;
  138. let additionalActivePick = undefined;
  139. if (isPicksWithActive(awaitedAdditionalPicks)) {
  140. additionalPicks = awaitedAdditionalPicks.items;
  141. additionalActivePick = awaitedAdditionalPicks.active;
  142. }
  143. else {
  144. additionalPicks = awaitedAdditionalPicks;
  145. }
  146. if (additionalPicks.length > 0 || !fastPicksApplied) {
  147. // If we do not have any activePick or additionalActivePick
  148. // we try to preserve the currently active pick from the
  149. // fast results. This fixes an issue where the user might
  150. // have made a pick active before the additional results
  151. // kick in.
  152. // See https://github.com/microsoft/vscode/issues/102480
  153. let fallbackActivePick = undefined;
  154. if (!activePick && !additionalActivePick) {
  155. const fallbackActivePickCandidate = picker.activeItems[0];
  156. if (fallbackActivePickCandidate && picks.indexOf(fallbackActivePickCandidate) !== -1) {
  157. fallbackActivePick = fallbackActivePickCandidate;
  158. }
  159. }
  160. applyPicks({
  161. items: [...picks, ...additionalPicks],
  162. active: activePick || additionalActivePick || fallbackActivePick
  163. });
  164. }
  165. }
  166. finally {
  167. if (!picksToken.isCancellationRequested) {
  168. picker.busy = false;
  169. }
  170. slowPicksApplied = true;
  171. }
  172. }))()
  173. ]);
  174. }
  175. // Fast Picks
  176. else if (!(providedPicks instanceof Promise)) {
  177. applyPicks(providedPicks);
  178. }
  179. // Slow Picks
  180. else {
  181. picker.busy = true;
  182. try {
  183. const awaitedPicks = yield providedPicks;
  184. if (picksToken.isCancellationRequested) {
  185. return;
  186. }
  187. applyPicks(awaitedPicks);
  188. }
  189. finally {
  190. if (!picksToken.isCancellationRequested) {
  191. picker.busy = false;
  192. }
  193. }
  194. }
  195. });
  196. disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
  197. updatePickerItems();
  198. // Accept the pick on accept and hide picker
  199. disposables.add(picker.onDidAccept(event => {
  200. const [item] = picker.selectedItems;
  201. if (typeof (item === null || item === void 0 ? void 0 : item.accept) === 'function') {
  202. if (!event.inBackground) {
  203. picker.hide(); // hide picker unless we accept in background
  204. }
  205. item.accept(picker.keyMods, event);
  206. }
  207. }));
  208. // Trigger the pick with button index if button triggered
  209. disposables.add(picker.onDidTriggerItemButton(({ button, item }) => __awaiter(this, void 0, void 0, function* () {
  210. var _b, _c;
  211. if (typeof item.trigger === 'function') {
  212. const buttonIndex = (_c = (_b = item.buttons) === null || _b === void 0 ? void 0 : _b.indexOf(button)) !== null && _c !== void 0 ? _c : -1;
  213. if (buttonIndex >= 0) {
  214. const result = item.trigger(buttonIndex, picker.keyMods);
  215. const action = (typeof result === 'number') ? result : yield result;
  216. if (token.isCancellationRequested) {
  217. return;
  218. }
  219. switch (action) {
  220. case TriggerAction.NO_ACTION:
  221. break;
  222. case TriggerAction.CLOSE_PICKER:
  223. picker.hide();
  224. break;
  225. case TriggerAction.REFRESH_PICKER:
  226. updatePickerItems();
  227. break;
  228. case TriggerAction.REMOVE_ITEM:
  229. const index = picker.items.indexOf(item);
  230. if (index !== -1) {
  231. const items = picker.items.slice();
  232. const removed = items.splice(index, 1);
  233. const activeItems = picker.activeItems.filter(activeItem => activeItem !== removed[0]);
  234. const keepScrollPositionBefore = picker.keepScrollPosition;
  235. picker.keepScrollPosition = true;
  236. picker.items = items;
  237. if (activeItems) {
  238. picker.activeItems = activeItems;
  239. }
  240. picker.keepScrollPosition = keepScrollPositionBefore;
  241. }
  242. break;
  243. }
  244. }
  245. }
  246. })));
  247. return disposables;
  248. }
  249. }
  250. PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY = 200; // timeout before we accept fast results before slow results are present