UNPKG

38.2 kBJavaScriptView Raw
1"use strict";
2var __extends = (this && this.__extends) || (function () {
3 var extendStatics = function (d, b) {
4 extendStatics = Object.setPrototypeOf ||
5 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
7 return extendStatics(d, b);
8 }
9 return function (d, b) {
10 extendStatics(d, b);
11 function __() { this.constructor = d; }
12 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
13 };
14})();
15var __assign = (this && this.__assign) || function () {
16 __assign = Object.assign || function(t) {
17 for (var s, i = 1, n = arguments.length; i < n; i++) {
18 s = arguments[i];
19 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
20 t[p] = s[p];
21 }
22 return t;
23 };
24 return __assign.apply(this, arguments);
25};
26Object.defineProperty(exports, "__esModule", { value: true });
27/*-----------------------------------------------------------------------------
28| Copyright (c) 2014-2017, PhosphorJS Contributors
29|
30| Distributed under the terms of the BSD 3-Clause License.
31|
32| The full license is in the file LICENSE, distributed with this software.
33|----------------------------------------------------------------------------*/
34var algorithm_1 = require("@phosphor/algorithm");
35var coreutils_1 = require("@phosphor/coreutils");
36var commands_1 = require("@phosphor/commands");
37var domutils_1 = require("@phosphor/domutils");
38var virtualdom_1 = require("@phosphor/virtualdom");
39var widget_1 = require("./widget");
40/**
41 * A widget which displays command items as a searchable palette.
42 */
43var CommandPalette = /** @class */ (function (_super) {
44 __extends(CommandPalette, _super);
45 /**
46 * Construct a new command palette.
47 *
48 * @param options - The options for initializing the palette.
49 */
50 function CommandPalette(options) {
51 var _this = _super.call(this, { node: Private.createNode() }) || this;
52 _this._activeIndex = -1;
53 _this._items = [];
54 _this._results = null;
55 _this.addClass('p-CommandPalette');
56 _this.setFlag(widget_1.Widget.Flag.DisallowLayout);
57 _this.commands = options.commands;
58 _this.renderer = options.renderer || CommandPalette.defaultRenderer;
59 _this.commands.commandChanged.connect(_this._onGenericChange, _this);
60 _this.commands.keyBindingChanged.connect(_this._onGenericChange, _this);
61 return _this;
62 }
63 /**
64 * Dispose of the resources held by the widget.
65 */
66 CommandPalette.prototype.dispose = function () {
67 this._items.length = 0;
68 this._results = null;
69 _super.prototype.dispose.call(this);
70 };
71 Object.defineProperty(CommandPalette.prototype, "searchNode", {
72 /**
73 * The command palette search node.
74 *
75 * #### Notes
76 * This is the node which contains the search-related elements.
77 */
78 get: function () {
79 return this.node.getElementsByClassName('p-CommandPalette-search')[0];
80 },
81 enumerable: true,
82 configurable: true
83 });
84 Object.defineProperty(CommandPalette.prototype, "inputNode", {
85 /**
86 * The command palette input node.
87 *
88 * #### Notes
89 * This is the actual input node for the search area.
90 */
91 get: function () {
92 return this.node.getElementsByClassName('p-CommandPalette-input')[0];
93 },
94 enumerable: true,
95 configurable: true
96 });
97 Object.defineProperty(CommandPalette.prototype, "contentNode", {
98 /**
99 * The command palette content node.
100 *
101 * #### Notes
102 * This is the node which holds the command item nodes.
103 *
104 * Modifying this node directly can lead to undefined behavior.
105 */
106 get: function () {
107 return this.node.getElementsByClassName('p-CommandPalette-content')[0];
108 },
109 enumerable: true,
110 configurable: true
111 });
112 Object.defineProperty(CommandPalette.prototype, "items", {
113 /**
114 * A read-only array of the command items in the palette.
115 */
116 get: function () {
117 return this._items;
118 },
119 enumerable: true,
120 configurable: true
121 });
122 /**
123 * Add a command item to the command palette.
124 *
125 * @param options - The options for creating the command item.
126 *
127 * @returns The command item added to the palette.
128 */
129 CommandPalette.prototype.addItem = function (options) {
130 // Create a new command item for the options.
131 var item = Private.createItem(this.commands, options);
132 // Add the item to the array.
133 this._items.push(item);
134 // Refresh the search results.
135 this.refresh();
136 // Return the item added to the palette.
137 return item;
138 };
139 /**
140 * Remove an item from the command palette.
141 *
142 * @param item - The item to remove from the palette.
143 *
144 * #### Notes
145 * This is a no-op if the item is not in the palette.
146 */
147 CommandPalette.prototype.removeItem = function (item) {
148 this.removeItemAt(this._items.indexOf(item));
149 };
150 /**
151 * Remove the item at a given index from the command palette.
152 *
153 * @param index - The index of the item to remove.
154 *
155 * #### Notes
156 * This is a no-op if the index is out of range.
157 */
158 CommandPalette.prototype.removeItemAt = function (index) {
159 // Remove the item from the array.
160 var item = algorithm_1.ArrayExt.removeAt(this._items, index);
161 // Bail if the index is out of range.
162 if (!item) {
163 return;
164 }
165 // Refresh the search results.
166 this.refresh();
167 };
168 /**
169 * Remove all items from the command palette.
170 */
171 CommandPalette.prototype.clearItems = function () {
172 // Bail if there is nothing to remove.
173 if (this._items.length === 0) {
174 return;
175 }
176 // Clear the array of items.
177 this._items.length = 0;
178 // Refresh the search results.
179 this.refresh();
180 };
181 /**
182 * Clear the search results and schedule an update.
183 *
184 * #### Notes
185 * This should be called whenever the search results of the palette
186 * should be updated.
187 *
188 * This is typically called automatically by the palette as needed,
189 * but can be called manually if the input text is programatically
190 * changed.
191 *
192 * The rendered results are updated asynchronously.
193 */
194 CommandPalette.prototype.refresh = function () {
195 this._results = null;
196 this.update();
197 };
198 /**
199 * Handle the DOM events for the command palette.
200 *
201 * @param event - The DOM event sent to the command palette.
202 *
203 * #### Notes
204 * This method implements the DOM `EventListener` interface and is
205 * called in response to events on the command palette's DOM node.
206 * It should not be called directly by user code.
207 */
208 CommandPalette.prototype.handleEvent = function (event) {
209 switch (event.type) {
210 case 'click':
211 this._evtClick(event);
212 break;
213 case 'keydown':
214 this._evtKeyDown(event);
215 break;
216 case 'input':
217 this.refresh();
218 break;
219 case 'focus':
220 case 'blur':
221 this._toggleFocused();
222 break;
223 }
224 };
225 /**
226 * A message handler invoked on a `'before-attach'` message.
227 */
228 CommandPalette.prototype.onBeforeAttach = function (msg) {
229 this.node.addEventListener('click', this);
230 this.node.addEventListener('keydown', this);
231 this.node.addEventListener('input', this);
232 this.node.addEventListener('focus', this, true);
233 this.node.addEventListener('blur', this, true);
234 };
235 /**
236 * A message handler invoked on an `'after-detach'` message.
237 */
238 CommandPalette.prototype.onAfterDetach = function (msg) {
239 this.node.removeEventListener('click', this);
240 this.node.removeEventListener('keydown', this);
241 this.node.removeEventListener('input', this);
242 this.node.removeEventListener('focus', this, true);
243 this.node.removeEventListener('blur', this, true);
244 };
245 /**
246 * A message handler invoked on an `'activate-request'` message.
247 */
248 CommandPalette.prototype.onActivateRequest = function (msg) {
249 if (this.isAttached) {
250 var input = this.inputNode;
251 input.focus();
252 input.select();
253 }
254 };
255 /**
256 * A message handler invoked on an `'update-request'` message.
257 */
258 CommandPalette.prototype.onUpdateRequest = function (msg) {
259 // Fetch the current query text and content node.
260 var query = this.inputNode.value;
261 var contentNode = this.contentNode;
262 // Ensure the search results are generated.
263 var results = this._results;
264 if (!results) {
265 // Generate and store the new search results.
266 results = this._results = Private.search(this._items, query);
267 // Reset the active index.
268 this._activeIndex = (query ? algorithm_1.ArrayExt.findFirstIndex(results, Private.canActivate) : -1);
269 }
270 // If there is no query and no results, clear the content.
271 if (!query && results.length === 0) {
272 virtualdom_1.VirtualDOM.render(null, contentNode);
273 return;
274 }
275 // If the is a query but no results, render the empty message.
276 if (query && results.length === 0) {
277 var content_1 = this.renderer.renderEmptyMessage({ query: query });
278 virtualdom_1.VirtualDOM.render(content_1, contentNode);
279 return;
280 }
281 // Create the render content for the search results.
282 var renderer = this.renderer;
283 var activeIndex = this._activeIndex;
284 var content = new Array(results.length);
285 for (var i = 0, n = results.length; i < n; ++i) {
286 var result = results[i];
287 if (result.type === 'header') {
288 var indices = result.indices;
289 var category = result.category;
290 content[i] = renderer.renderHeader({ category: category, indices: indices });
291 }
292 else {
293 var item = result.item;
294 var indices = result.indices;
295 var active = i === activeIndex;
296 content[i] = renderer.renderItem({ item: item, indices: indices, active: active });
297 }
298 }
299 // Render the search result content.
300 virtualdom_1.VirtualDOM.render(content, contentNode);
301 // Adjust the scroll position as needed.
302 if (activeIndex < 0 || activeIndex >= results.length) {
303 contentNode.scrollTop = 0;
304 }
305 else {
306 var element = contentNode.children[activeIndex];
307 domutils_1.ElementExt.scrollIntoViewIfNeeded(contentNode, element);
308 }
309 };
310 /**
311 * Handle the `'click'` event for the command palette.
312 */
313 CommandPalette.prototype._evtClick = function (event) {
314 // Bail if the click is not the left button.
315 if (event.button !== 0) {
316 return;
317 }
318 // Find the index of the item which was clicked.
319 var index = algorithm_1.ArrayExt.findFirstIndex(this.contentNode.children, function (node) {
320 return node.contains(event.target);
321 });
322 // Bail if the click was not on an item.
323 if (index === -1) {
324 return;
325 }
326 // Kill the event when a content item is clicked.
327 event.preventDefault();
328 event.stopPropagation();
329 // Execute the item if possible.
330 this._execute(index);
331 };
332 /**
333 * Handle the `'keydown'` event for the command palette.
334 */
335 CommandPalette.prototype._evtKeyDown = function (event) {
336 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
337 return;
338 }
339 switch (event.keyCode) {
340 case 13: // Enter
341 event.preventDefault();
342 event.stopPropagation();
343 this._execute(this._activeIndex);
344 break;
345 case 38: // Up Arrow
346 event.preventDefault();
347 event.stopPropagation();
348 this._activatePreviousItem();
349 break;
350 case 40: // Down Arrow
351 event.preventDefault();
352 event.stopPropagation();
353 this._activateNextItem();
354 break;
355 }
356 };
357 /**
358 * Activate the next enabled command item.
359 */
360 CommandPalette.prototype._activateNextItem = function () {
361 // Bail if there are no search results.
362 if (!this._results || this._results.length === 0) {
363 return;
364 }
365 // Find the next enabled item index.
366 var ai = this._activeIndex;
367 var n = this._results.length;
368 var start = ai < n - 1 ? ai + 1 : 0;
369 var stop = start === 0 ? n - 1 : start - 1;
370 this._activeIndex = algorithm_1.ArrayExt.findFirstIndex(this._results, Private.canActivate, start, stop);
371 // Schedule an update of the items.
372 this.update();
373 };
374 /**
375 * Activate the previous enabled command item.
376 */
377 CommandPalette.prototype._activatePreviousItem = function () {
378 // Bail if there are no search results.
379 if (!this._results || this._results.length === 0) {
380 return;
381 }
382 // Find the previous enabled item index.
383 var ai = this._activeIndex;
384 var n = this._results.length;
385 var start = ai <= 0 ? n - 1 : ai - 1;
386 var stop = start === n - 1 ? 0 : start + 1;
387 this._activeIndex = algorithm_1.ArrayExt.findLastIndex(this._results, Private.canActivate, start, stop);
388 // Schedule an update of the items.
389 this.update();
390 };
391 /**
392 * Execute the command item at the given index, if possible.
393 */
394 CommandPalette.prototype._execute = function (index) {
395 // Bail if there are no search results.
396 if (!this._results) {
397 return;
398 }
399 // Bail if the index is out of range.
400 var part = this._results[index];
401 if (!part) {
402 return;
403 }
404 // Update the search text if the item is a header.
405 if (part.type === 'header') {
406 var input = this.inputNode;
407 input.value = part.category.toLowerCase() + " ";
408 input.focus();
409 this.refresh();
410 return;
411 }
412 // Bail if item is not enabled.
413 if (!part.item.isEnabled) {
414 return;
415 }
416 // Execute the item.
417 this.commands.execute(part.item.command, part.item.args);
418 // Clear the query text.
419 this.inputNode.value = '';
420 // Refresh the search results.
421 this.refresh();
422 };
423 /**
424 * Toggle the focused modifier based on the input node focus state.
425 */
426 CommandPalette.prototype._toggleFocused = function () {
427 var focused = document.activeElement === this.inputNode;
428 this.toggleClass('p-mod-focused', focused);
429 };
430 /**
431 * A signal handler for generic command changes.
432 */
433 CommandPalette.prototype._onGenericChange = function () {
434 this.refresh();
435 };
436 return CommandPalette;
437}(widget_1.Widget));
438exports.CommandPalette = CommandPalette;
439/**
440 * The namespace for the `CommandPalette` class statics.
441 */
442(function (CommandPalette) {
443 /**
444 * The default implementation of `IRenderer`.
445 */
446 var Renderer = /** @class */ (function () {
447 function Renderer() {
448 }
449 /**
450 * Render the virtual element for a command palette header.
451 *
452 * @param data - The data to use for rendering the header.
453 *
454 * @returns A virtual element representing the header.
455 */
456 Renderer.prototype.renderHeader = function (data) {
457 var content = this.formatHeader(data);
458 return virtualdom_1.h.li({ className: 'p-CommandPalette-header' }, content);
459 };
460 /**
461 * Render the virtual element for a command palette item.
462 *
463 * @param data - The data to use for rendering the item.
464 *
465 * @returns A virtual element representing the item.
466 */
467 Renderer.prototype.renderItem = function (data) {
468 var className = this.createItemClass(data);
469 var dataset = this.createItemDataset(data);
470 return (virtualdom_1.h.li({ className: className, dataset: dataset }, this.renderItemIcon(data), this.renderItemContent(data), this.renderItemShortcut(data)));
471 };
472 /**
473 * Render the empty results message for a command palette.
474 *
475 * @param data - The data to use for rendering the message.
476 *
477 * @returns A virtual element representing the message.
478 */
479 Renderer.prototype.renderEmptyMessage = function (data) {
480 var content = this.formatEmptyMessage(data);
481 return virtualdom_1.h.li({ className: 'p-CommandPalette-emptyMessage' }, content);
482 };
483 /**
484 * Render the icon for a command palette item.
485 *
486 * @param data - The data to use for rendering the icon.
487 *
488 * @returns A virtual element representing the icon.
489 */
490 Renderer.prototype.renderItemIcon = function (data) {
491 var className = this.createIconClass(data);
492 return virtualdom_1.h.div({ className: className }, data.item.iconLabel);
493 };
494 /**
495 * Render the content for a command palette item.
496 *
497 * @param data - The data to use for rendering the content.
498 *
499 * @returns A virtual element representing the content.
500 */
501 Renderer.prototype.renderItemContent = function (data) {
502 return (virtualdom_1.h.div({ className: 'p-CommandPalette-itemContent' }, this.renderItemLabel(data), this.renderItemCaption(data)));
503 };
504 /**
505 * Render the label for a command palette item.
506 *
507 * @param data - The data to use for rendering the label.
508 *
509 * @returns A virtual element representing the label.
510 */
511 Renderer.prototype.renderItemLabel = function (data) {
512 var content = this.formatItemLabel(data);
513 return virtualdom_1.h.div({ className: 'p-CommandPalette-itemLabel' }, content);
514 };
515 /**
516 * Render the caption for a command palette item.
517 *
518 * @param data - The data to use for rendering the caption.
519 *
520 * @returns A virtual element representing the caption.
521 */
522 Renderer.prototype.renderItemCaption = function (data) {
523 var content = this.formatItemCaption(data);
524 return virtualdom_1.h.div({ className: 'p-CommandPalette-itemCaption' }, content);
525 };
526 /**
527 * Render the shortcut for a command palette item.
528 *
529 * @param data - The data to use for rendering the shortcut.
530 *
531 * @returns A virtual element representing the shortcut.
532 */
533 Renderer.prototype.renderItemShortcut = function (data) {
534 var content = this.formatItemShortcut(data);
535 return virtualdom_1.h.div({ className: 'p-CommandPalette-itemShortcut' }, content);
536 };
537 /**
538 * Create the class name for the command palette item.
539 *
540 * @param data - The data to use for the class name.
541 *
542 * @returns The full class name for the command palette item.
543 */
544 Renderer.prototype.createItemClass = function (data) {
545 // Set up the initial class name.
546 var name = 'p-CommandPalette-item';
547 // Add the boolean state classes.
548 if (!data.item.isEnabled) {
549 name += ' p-mod-disabled';
550 }
551 if (data.item.isToggled) {
552 name += ' p-mod-toggled';
553 }
554 if (data.active) {
555 name += ' p-mod-active';
556 }
557 // Add the extra class.
558 var extra = data.item.className;
559 if (extra) {
560 name += " " + extra;
561 }
562 // Return the complete class name.
563 return name;
564 };
565 /**
566 * Create the dataset for the command palette item.
567 *
568 * @param data - The data to use for creating the dataset.
569 *
570 * @returns The dataset for the command palette item.
571 */
572 Renderer.prototype.createItemDataset = function (data) {
573 return __assign({}, data.item.dataset, { command: data.item.command });
574 };
575 /**
576 * Create the class name for the command item icon.
577 *
578 * @param data - The data to use for the class name.
579 *
580 * @returns The full class name for the item icon.
581 */
582 Renderer.prototype.createIconClass = function (data) {
583 var name = 'p-CommandPalette-itemIcon';
584 var extra = data.item.iconClass;
585 return extra ? name + " " + extra : name;
586 };
587 /**
588 * Create the render content for the header node.
589 *
590 * @param data - The data to use for the header content.
591 *
592 * @returns The content to add to the header node.
593 */
594 Renderer.prototype.formatHeader = function (data) {
595 if (!data.indices || data.indices.length === 0) {
596 return data.category;
597 }
598 return algorithm_1.StringExt.highlight(data.category, data.indices, virtualdom_1.h.mark);
599 };
600 /**
601 * Create the render content for the empty message node.
602 *
603 * @param data - The data to use for the empty message content.
604 *
605 * @returns The content to add to the empty message node.
606 */
607 Renderer.prototype.formatEmptyMessage = function (data) {
608 return "No commands found that match '" + data.query + "'";
609 };
610 /**
611 * Create the render content for the item shortcut node.
612 *
613 * @param data - The data to use for the shortcut content.
614 *
615 * @returns The content to add to the shortcut node.
616 */
617 Renderer.prototype.formatItemShortcut = function (data) {
618 var kb = data.item.keyBinding;
619 return kb ? kb.keys.map(commands_1.CommandRegistry.formatKeystroke).join(', ') : null;
620 };
621 /**
622 * Create the render content for the item label node.
623 *
624 * @param data - The data to use for the label content.
625 *
626 * @returns The content to add to the label node.
627 */
628 Renderer.prototype.formatItemLabel = function (data) {
629 if (!data.indices || data.indices.length === 0) {
630 return data.item.label;
631 }
632 return algorithm_1.StringExt.highlight(data.item.label, data.indices, virtualdom_1.h.mark);
633 };
634 /**
635 * Create the render content for the item caption node.
636 *
637 * @param data - The data to use for the caption content.
638 *
639 * @returns The content to add to the caption node.
640 */
641 Renderer.prototype.formatItemCaption = function (data) {
642 return data.item.caption;
643 };
644 return Renderer;
645 }());
646 CommandPalette.Renderer = Renderer;
647 /**
648 * The default `Renderer` instance.
649 */
650 CommandPalette.defaultRenderer = new Renderer();
651})(CommandPalette = exports.CommandPalette || (exports.CommandPalette = {}));
652exports.CommandPalette = CommandPalette;
653/**
654 * The namespace for the module implementation details.
655 */
656var Private;
657(function (Private) {
658 /**
659 * Create the DOM node for a command palette.
660 */
661 function createNode() {
662 var node = document.createElement('div');
663 var search = document.createElement('div');
664 var wrapper = document.createElement('div');
665 var input = document.createElement('input');
666 var content = document.createElement('ul');
667 search.className = 'p-CommandPalette-search';
668 wrapper.className = 'p-CommandPalette-wrapper';
669 input.className = 'p-CommandPalette-input';
670 content.className = 'p-CommandPalette-content';
671 input.spellcheck = false;
672 wrapper.appendChild(input);
673 search.appendChild(wrapper);
674 node.appendChild(search);
675 node.appendChild(content);
676 return node;
677 }
678 Private.createNode = createNode;
679 /**
680 * Create a new command item from a command registry and options.
681 */
682 function createItem(commands, options) {
683 return new CommandItem(commands, options);
684 }
685 Private.createItem = createItem;
686 /**
687 * Search an array of command items for fuzzy matches.
688 */
689 function search(items, query) {
690 // Fuzzy match the items for the query.
691 var scores = matchItems(items, query);
692 // Sort the items based on their score.
693 scores.sort(scoreCmp);
694 // Create the results for the search.
695 return createResults(scores);
696 }
697 Private.search = search;
698 /**
699 * Test whether a result item can be activated.
700 */
701 function canActivate(result) {
702 return result.type === 'item' && result.item.isEnabled;
703 }
704 Private.canActivate = canActivate;
705 /**
706 * Normalize a category for a command item.
707 */
708 function normalizeCategory(category) {
709 return category.trim().replace(/\s+/g, ' ');
710 }
711 /**
712 * Normalize the query text for a fuzzy search.
713 */
714 function normalizeQuery(text) {
715 return text.replace(/\s+/g, '').toLowerCase();
716 }
717 /**
718 * Perform a fuzzy match on an array of command items.
719 */
720 function matchItems(items, query) {
721 // Normalize the query text to lower case with no whitespace.
722 query = normalizeQuery(query);
723 // Create the array to hold the scores.
724 var scores = [];
725 // Iterate over the items and match against the query.
726 for (var i = 0, n = items.length; i < n; ++i) {
727 // Ignore items which are not visible.
728 var item = items[i];
729 if (!item.isVisible) {
730 continue;
731 }
732 // If the query is empty, all items are matched by default.
733 if (!query) {
734 scores.push({
735 matchType: 3 /* Default */,
736 categoryIndices: null,
737 labelIndices: null,
738 score: 0, item: item
739 });
740 continue;
741 }
742 // Run the fuzzy search for the item and query.
743 var score = fuzzySearch(item, query);
744 // Ignore the item if it is not a match.
745 if (!score) {
746 continue;
747 }
748 // Penalize disabled items.
749 // TODO - push disabled items all the way down in sort cmp?
750 if (!item.isEnabled) {
751 score.score += 1000;
752 }
753 // Add the score to the results.
754 scores.push(score);
755 }
756 // Return the final array of scores.
757 return scores;
758 }
759 /**
760 * Perform a fuzzy search on a single command item.
761 */
762 function fuzzySearch(item, query) {
763 // Create the source text to be searched.
764 var category = item.category.toLowerCase();
765 var label = item.label.toLowerCase();
766 var source = category + " " + label;
767 // Set up the match score and indices array.
768 var score = Infinity;
769 var indices = null;
770 // The regex for search word boundaries
771 var rgx = /\b\w/g;
772 // Search the source by word boundary.
773 while (true) {
774 // Find the next word boundary in the source.
775 var rgxMatch = rgx.exec(source);
776 // Break if there is no more source context.
777 if (!rgxMatch) {
778 break;
779 }
780 // Run the string match on the relevant substring.
781 var match = algorithm_1.StringExt.matchSumOfDeltas(source, query, rgxMatch.index);
782 // Break if there is no match.
783 if (!match) {
784 break;
785 }
786 // Update the match if the score is better.
787 if (match && match.score <= score) {
788 score = match.score;
789 indices = match.indices;
790 }
791 }
792 // Bail if there was no match.
793 if (!indices || score === Infinity) {
794 return null;
795 }
796 // Compute the pivot index between category and label text.
797 var pivot = category.length + 1;
798 // Find the slice index to separate matched indices.
799 var j = algorithm_1.ArrayExt.lowerBound(indices, pivot, function (a, b) { return a - b; });
800 // Extract the matched category and label indices.
801 var categoryIndices = indices.slice(0, j);
802 var labelIndices = indices.slice(j);
803 // Adjust the label indices for the pivot offset.
804 for (var i = 0, n = labelIndices.length; i < n; ++i) {
805 labelIndices[i] -= pivot;
806 }
807 // Handle a pure label match.
808 if (categoryIndices.length === 0) {
809 return {
810 matchType: 0 /* Label */,
811 categoryIndices: null,
812 labelIndices: labelIndices,
813 score: score, item: item
814 };
815 }
816 // Handle a pure category match.
817 if (labelIndices.length === 0) {
818 return {
819 matchType: 1 /* Category */,
820 categoryIndices: categoryIndices,
821 labelIndices: null,
822 score: score, item: item
823 };
824 }
825 // Handle a split match.
826 return {
827 matchType: 2 /* Split */,
828 categoryIndices: categoryIndices,
829 labelIndices: labelIndices,
830 score: score, item: item
831 };
832 }
833 /**
834 * A sort comparison function for a match score.
835 */
836 function scoreCmp(a, b) {
837 // First compare based on the match type
838 var m1 = a.matchType - b.matchType;
839 if (m1 !== 0) {
840 return m1;
841 }
842 // Otherwise, compare based on the match score.
843 var d1 = a.score - b.score;
844 if (d1 !== 0) {
845 return d1;
846 }
847 // Find the match index based on the match type.
848 var i1 = 0;
849 var i2 = 0;
850 switch (a.matchType) {
851 case 0 /* Label */:
852 i1 = a.labelIndices[0];
853 i2 = b.labelIndices[0];
854 break;
855 case 1 /* Category */:
856 case 2 /* Split */:
857 i1 = a.categoryIndices[0];
858 i2 = b.categoryIndices[0];
859 break;
860 }
861 // Compare based on the match index.
862 if (i1 !== i2) {
863 return i1 - i2;
864 }
865 // Otherwise, compare by category.
866 var d2 = a.item.category.localeCompare(b.item.category);
867 if (d2 !== 0) {
868 return d2;
869 }
870 // Otherwise, compare by rank.
871 var r1 = a.item.rank;
872 var r2 = b.item.rank;
873 if (r1 !== r2) {
874 return r1 < r2 ? -1 : 1; // Infinity safe
875 }
876 // Finally, compare by label.
877 return a.item.label.localeCompare(b.item.label);
878 }
879 /**
880 * Create the results from an array of sorted scores.
881 */
882 function createResults(scores) {
883 // Set up an array to track which scores have been visited.
884 var visited = new Array(scores.length);
885 algorithm_1.ArrayExt.fill(visited, false);
886 // Set up the search results array.
887 var results = [];
888 // Iterate over each score in the array.
889 for (var i = 0, n = scores.length; i < n; ++i) {
890 // Ignore a score which has already been processed.
891 if (visited[i]) {
892 continue;
893 }
894 // Extract the current item and indices.
895 var _a = scores[i], item = _a.item, categoryIndices = _a.categoryIndices;
896 // Extract the category for the current item.
897 var category = item.category;
898 // Add the header result for the category.
899 results.push({ type: 'header', category: category, indices: categoryIndices });
900 // Find the rest of the scores with the same category.
901 for (var j = i; j < n; ++j) {
902 // Ignore a score which has already been processed.
903 if (visited[j]) {
904 continue;
905 }
906 // Extract the data for the current score.
907 var _b = scores[j], item_1 = _b.item, labelIndices = _b.labelIndices;
908 // Ignore an item with a different category.
909 if (item_1.category !== category) {
910 continue;
911 }
912 // Create the item result for the score.
913 results.push({ type: 'item', item: item_1, indices: labelIndices });
914 // Mark the score as processed.
915 visited[j] = true;
916 }
917 }
918 // Return the final results.
919 return results;
920 }
921 /**
922 * A concrete implementation of `CommandPalette.IItem`.
923 */
924 var CommandItem = /** @class */ (function () {
925 /**
926 * Construct a new command item.
927 */
928 function CommandItem(commands, options) {
929 this._commands = commands;
930 this.category = normalizeCategory(options.category);
931 this.command = options.command;
932 this.args = options.args || coreutils_1.JSONExt.emptyObject;
933 this.rank = options.rank !== undefined ? options.rank : Infinity;
934 }
935 Object.defineProperty(CommandItem.prototype, "label", {
936 /**
937 * The display label for the command item.
938 */
939 get: function () {
940 return this._commands.label(this.command, this.args);
941 },
942 enumerable: true,
943 configurable: true
944 });
945 Object.defineProperty(CommandItem.prototype, "iconClass", {
946 /**
947 * The icon class for the command item.
948 */
949 get: function () {
950 return this._commands.iconClass(this.command, this.args);
951 },
952 enumerable: true,
953 configurable: true
954 });
955 Object.defineProperty(CommandItem.prototype, "iconLabel", {
956 /**
957 * The icon label for the command item.
958 */
959 get: function () {
960 return this._commands.iconLabel(this.command, this.args);
961 },
962 enumerable: true,
963 configurable: true
964 });
965 Object.defineProperty(CommandItem.prototype, "caption", {
966 /**
967 * The display caption for the command item.
968 */
969 get: function () {
970 return this._commands.caption(this.command, this.args);
971 },
972 enumerable: true,
973 configurable: true
974 });
975 Object.defineProperty(CommandItem.prototype, "className", {
976 /**
977 * The extra class name for the command item.
978 */
979 get: function () {
980 return this._commands.className(this.command, this.args);
981 },
982 enumerable: true,
983 configurable: true
984 });
985 Object.defineProperty(CommandItem.prototype, "dataset", {
986 /**
987 * The dataset for the command item.
988 */
989 get: function () {
990 return this._commands.dataset(this.command, this.args);
991 },
992 enumerable: true,
993 configurable: true
994 });
995 Object.defineProperty(CommandItem.prototype, "isEnabled", {
996 /**
997 * Whether the command item is enabled.
998 */
999 get: function () {
1000 return this._commands.isEnabled(this.command, this.args);
1001 },
1002 enumerable: true,
1003 configurable: true
1004 });
1005 Object.defineProperty(CommandItem.prototype, "isToggled", {
1006 /**
1007 * Whether the command item is toggled.
1008 */
1009 get: function () {
1010 return this._commands.isToggled(this.command, this.args);
1011 },
1012 enumerable: true,
1013 configurable: true
1014 });
1015 Object.defineProperty(CommandItem.prototype, "isVisible", {
1016 /**
1017 * Whether the command item is visible.
1018 */
1019 get: function () {
1020 return this._commands.isVisible(this.command, this.args);
1021 },
1022 enumerable: true,
1023 configurable: true
1024 });
1025 Object.defineProperty(CommandItem.prototype, "keyBinding", {
1026 /**
1027 * The key binding for the command item.
1028 */
1029 get: function () {
1030 var _a = this, command = _a.command, args = _a.args;
1031 return algorithm_1.ArrayExt.findLastValue(this._commands.keyBindings, function (kb) {
1032 return kb.command === command && coreutils_1.JSONExt.deepEqual(kb.args, args);
1033 }) || null;
1034 },
1035 enumerable: true,
1036 configurable: true
1037 });
1038 return CommandItem;
1039 }());
1040})(Private || (Private = {}));