commandsQuickAccess.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { toErrorMessage } from '../../../base/common/errorMessage.js';
  24. import { isPromiseCanceledError } from '../../../base/common/errors.js';
  25. import { matchesContiguousSubString, matchesPrefix, matchesWords, or } from '../../../base/common/filters.js';
  26. import { Disposable } from '../../../base/common/lifecycle.js';
  27. import { LRUCache } from '../../../base/common/map.js';
  28. import Severity from '../../../base/common/severity.js';
  29. import { withNullAsUndefined } from '../../../base/common/types.js';
  30. import { localize } from '../../../nls.js';
  31. import { ICommandService } from '../../commands/common/commands.js';
  32. import { IConfigurationService } from '../../configuration/common/configuration.js';
  33. import { IDialogService } from '../../dialogs/common/dialogs.js';
  34. import { IInstantiationService } from '../../instantiation/common/instantiation.js';
  35. import { IKeybindingService } from '../../keybinding/common/keybinding.js';
  36. import { PickerQuickAccessProvider } from './pickerQuickAccess.js';
  37. import { IStorageService } from '../../storage/common/storage.js';
  38. import { ITelemetryService } from '../../telemetry/common/telemetry.js';
  39. let AbstractCommandsQuickAccessProvider = class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider {
  40. constructor(options, instantiationService, keybindingService, commandService, telemetryService, dialogService) {
  41. super(AbstractCommandsQuickAccessProvider.PREFIX, options);
  42. this.instantiationService = instantiationService;
  43. this.keybindingService = keybindingService;
  44. this.commandService = commandService;
  45. this.telemetryService = telemetryService;
  46. this.dialogService = dialogService;
  47. this.commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory));
  48. this.options = options;
  49. }
  50. _getPicks(filter, disposables, token) {
  51. return __awaiter(this, void 0, void 0, function* () {
  52. // Ask subclass for all command picks
  53. const allCommandPicks = yield this.getCommandPicks(disposables, token);
  54. if (token.isCancellationRequested) {
  55. return [];
  56. }
  57. // Filter
  58. const filteredCommandPicks = [];
  59. for (const commandPick of allCommandPicks) {
  60. const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label));
  61. const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined;
  62. // Add if matching in label or alias
  63. if (labelHighlights || aliasHighlights) {
  64. commandPick.highlights = {
  65. label: labelHighlights,
  66. detail: this.options.showAlias ? aliasHighlights : undefined
  67. };
  68. filteredCommandPicks.push(commandPick);
  69. }
  70. // Also add if we have a 100% command ID match
  71. else if (filter === commandPick.commandId) {
  72. filteredCommandPicks.push(commandPick);
  73. }
  74. }
  75. // Add description to commands that have duplicate labels
  76. const mapLabelToCommand = new Map();
  77. for (const commandPick of filteredCommandPicks) {
  78. const existingCommandForLabel = mapLabelToCommand.get(commandPick.label);
  79. if (existingCommandForLabel) {
  80. commandPick.description = commandPick.commandId;
  81. existingCommandForLabel.description = existingCommandForLabel.commandId;
  82. }
  83. else {
  84. mapLabelToCommand.set(commandPick.label, commandPick);
  85. }
  86. }
  87. // Sort by MRU order and fallback to name otherwise
  88. filteredCommandPicks.sort((commandPickA, commandPickB) => {
  89. const commandACounter = this.commandsHistory.peek(commandPickA.commandId);
  90. const commandBCounter = this.commandsHistory.peek(commandPickB.commandId);
  91. if (commandACounter && commandBCounter) {
  92. return commandACounter > commandBCounter ? -1 : 1; // use more recently used command before older
  93. }
  94. if (commandACounter) {
  95. return -1; // first command was used, so it wins over the non used one
  96. }
  97. if (commandBCounter) {
  98. return 1; // other command was used so it wins over the command
  99. }
  100. // both commands were never used, so we sort by name
  101. return commandPickA.label.localeCompare(commandPickB.label);
  102. });
  103. const commandPicks = [];
  104. let addSeparator = false;
  105. for (let i = 0; i < filteredCommandPicks.length; i++) {
  106. const commandPick = filteredCommandPicks[i];
  107. const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
  108. const ariaLabel = keybinding ?
  109. localize('commandPickAriaLabelWithKeybinding', "{0}, {1}", commandPick.label, keybinding.getAriaLabel()) :
  110. commandPick.label;
  111. // Separator: recently used
  112. if (i === 0 && this.commandsHistory.peek(commandPick.commandId)) {
  113. commandPicks.push({ type: 'separator', label: localize('recentlyUsed', "recently used") });
  114. addSeparator = true;
  115. }
  116. // Separator: other commands
  117. if (i !== 0 && addSeparator && !this.commandsHistory.peek(commandPick.commandId)) {
  118. commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") });
  119. addSeparator = false; // only once
  120. }
  121. // Command
  122. commandPicks.push(Object.assign(Object.assign({}, commandPick), { ariaLabel, detail: this.options.showAlias && commandPick.commandAlias !== commandPick.label ? commandPick.commandAlias : undefined, keybinding, accept: () => __awaiter(this, void 0, void 0, function* () {
  123. // Add to history
  124. this.commandsHistory.push(commandPick.commandId);
  125. // Telementry
  126. this.telemetryService.publicLog2('workbenchActionExecuted', {
  127. id: commandPick.commandId,
  128. from: 'quick open'
  129. });
  130. // Run
  131. try {
  132. yield this.commandService.executeCommand(commandPick.commandId);
  133. }
  134. catch (error) {
  135. if (!isPromiseCanceledError(error)) {
  136. this.dialogService.show(Severity.Error, localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error)));
  137. }
  138. }
  139. }) }));
  140. }
  141. return commandPicks;
  142. });
  143. }
  144. };
  145. AbstractCommandsQuickAccessProvider.PREFIX = '>';
  146. AbstractCommandsQuickAccessProvider.WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString);
  147. AbstractCommandsQuickAccessProvider = __decorate([
  148. __param(1, IInstantiationService),
  149. __param(2, IKeybindingService),
  150. __param(3, ICommandService),
  151. __param(4, ITelemetryService),
  152. __param(5, IDialogService)
  153. ], AbstractCommandsQuickAccessProvider);
  154. export { AbstractCommandsQuickAccessProvider };
  155. let CommandsHistory = class CommandsHistory extends Disposable {
  156. constructor(storageService, configurationService) {
  157. super();
  158. this.storageService = storageService;
  159. this.configurationService = configurationService;
  160. this.configuredCommandsHistoryLength = 0;
  161. this.updateConfiguration();
  162. this.load();
  163. this.registerListeners();
  164. }
  165. registerListeners() {
  166. this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()));
  167. }
  168. updateConfiguration() {
  169. this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService);
  170. if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) {
  171. CommandsHistory.cache.limit = this.configuredCommandsHistoryLength;
  172. CommandsHistory.saveState(this.storageService);
  173. }
  174. }
  175. load() {
  176. const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, 0 /* GLOBAL */);
  177. let serializedCache;
  178. if (raw) {
  179. try {
  180. serializedCache = JSON.parse(raw);
  181. }
  182. catch (error) {
  183. // invalid data
  184. }
  185. }
  186. const cache = CommandsHistory.cache = new LRUCache(this.configuredCommandsHistoryLength, 1);
  187. if (serializedCache) {
  188. let entries;
  189. if (serializedCache.usesLRU) {
  190. entries = serializedCache.entries;
  191. }
  192. else {
  193. entries = serializedCache.entries.sort((a, b) => a.value - b.value);
  194. }
  195. entries.forEach(entry => cache.set(entry.key, entry.value));
  196. }
  197. CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, 0 /* GLOBAL */, CommandsHistory.counter);
  198. }
  199. push(commandId) {
  200. if (!CommandsHistory.cache) {
  201. return;
  202. }
  203. CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command
  204. CommandsHistory.saveState(this.storageService);
  205. }
  206. peek(commandId) {
  207. var _a;
  208. return (_a = CommandsHistory.cache) === null || _a === void 0 ? void 0 : _a.peek(commandId);
  209. }
  210. static saveState(storageService) {
  211. if (!CommandsHistory.cache) {
  212. return;
  213. }
  214. const serializedCache = { usesLRU: true, entries: [] };
  215. CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value }));
  216. storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), 0 /* GLOBAL */, 0 /* USER */);
  217. storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, 0 /* GLOBAL */, 0 /* USER */);
  218. }
  219. static getConfiguredCommandHistoryLength(configurationService) {
  220. var _a, _b;
  221. const config = configurationService.getValue();
  222. const configuredCommandHistoryLength = (_b = (_a = config.workbench) === null || _a === void 0 ? void 0 : _a.commandPalette) === null || _b === void 0 ? void 0 : _b.history;
  223. if (typeof configuredCommandHistoryLength === 'number') {
  224. return configuredCommandHistoryLength;
  225. }
  226. return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH;
  227. }
  228. };
  229. CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH = 50;
  230. CommandsHistory.PREF_KEY_CACHE = 'commandPalette.mru.cache';
  231. CommandsHistory.PREF_KEY_COUNTER = 'commandPalette.mru.counter';
  232. CommandsHistory.counter = 1;
  233. CommandsHistory = __decorate([
  234. __param(0, IStorageService),
  235. __param(1, IConfigurationService)
  236. ], CommandsHistory);
  237. export { CommandsHistory };