Skip to content

Commit

Permalink
Merge pull request #706 from JasonWeill/interleave-search-results
Browse files Browse the repository at this point in the history
Interleaves search results across categories
  • Loading branch information
JasonWeill authored Jun 17, 2024
2 parents 276f786 + 102122f commit d98765d
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 33 deletions.
41 changes: 8 additions & 33 deletions packages/widgets/src/commandpalette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1374,50 +1374,25 @@ namespace Private {
* Create the results from an array of sorted scores.
*/
function createResults(scores: IScore[]): SearchResult[] {
// Set up an array to track which scores have been visited.
let visited = new Array(scores.length);
ArrayExt.fill(visited, false);

// Set up the search results array.
let results: SearchResult[] = [];

// Iterate over each score in the array.
for (let i = 0, n = scores.length; i < n; ++i) {
// Ignore a score which has already been processed.
if (visited[i]) {
continue;
}

// Extract the current item and indices.
let { item, categoryIndices } = scores[i];
let { item, categoryIndices, labelIndices } = scores[i];

// Extract the category for the current item.
let category = item.category;

// Add the header result for the category.
results.push({ type: 'header', category, indices: categoryIndices });

// Find the rest of the scores with the same category.
for (let j = i; j < n; ++j) {
// Ignore a score which has already been processed.
if (visited[j]) {
continue;
}

// Extract the data for the current score.
let { item, labelIndices } = scores[j];

// Ignore an item with a different category.
if (item.category !== category) {
continue;
}

// Create the item result for the score.
results.push({ type: 'item', item, indices: labelIndices });

// Mark the score as processed.
visited[j] = true;
// Is this the same category as the preceding result?
if (i === 0 || category !== scores[i - 1].item.category) {
// Add the header result for the category.
results.push({ type: 'header', category, indices: categoryIndices });
}

// Create the item result for the score.
results.push({ type: 'item', item, indices: labelIndices });
}

// Return the final results.
Expand Down
234 changes: 234 additions & 0 deletions packages/widgets/tests/src/commandpalette.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,240 @@ describe('@lumino/widgets', () => {
MessageLoop.flush();
expect(items()).to.have.length(0);
});

it('should search a list of commands', () => {
// Add several commands to the command registry and the palette
commands.addCommand('example:cut', {
label: 'Cut',
mnemonic: 1,
iconClass: 'fa fa-cut',
execute: () => {
console.log('Cut');
}
});

commands.addCommand('example:copy', {
label: 'Copy File',
mnemonic: 0,
iconClass: 'fa fa-copy',
execute: () => {
console.log('Copy');
}
});

commands.addCommand('example:paste', {
label: 'Paste',
mnemonic: 0,
iconClass: 'fa fa-paste',
execute: () => {
console.log('Paste');
}
});

commands.addCommand('example:new-tab', {
label: 'New Tab',
mnemonic: 0,
caption: 'Open a new tab',
execute: () => {
console.log('New Tab');
}
});

commands.addCommand('example:close-tab', {
label: 'Close Tab',
mnemonic: 2,
caption: 'Close the current tab',
execute: () => {
console.log('Close Tab');
}
});

commands.addCommand('example:save-on-exit', {
label: 'Save on Exit',
mnemonic: 0,
caption: 'Toggle the save on exit flag',
execute: () => {
console.log('Save on Exit');
}
});

commands.addCommand('example:open-task-manager', {
label: 'Task Manager',
mnemonic: 5,
isEnabled: () => false,
execute: () => {}
});

commands.addCommand('example:close', {
label: 'Close',
mnemonic: 0,
iconClass: 'fa fa-close',
execute: () => {
console.log('Close');
}
});

commands.addCommand('example:one', {
label: 'One',
execute: () => {
console.log('One');
}
});

commands.addCommand('example:two', {
label: 'Two',
execute: () => {
console.log('Two');
}
});

commands.addCommand('example:three', {
label: 'Three',
execute: () => {
console.log('Three');
}
});

commands.addCommand('example:four', {
label: 'Four',
execute: () => {
console.log('Four');
}
});

commands.addCommand('example:black', {
label: 'Black',
execute: () => {
console.log('Black');
}
});

commands.addCommand('example:clear-cell', {
label: 'Clear Cell',
execute: () => {
console.log('Clear Cell');
}
});

commands.addCommand('example:cut-cells', {
label: 'Cut Cell(s)',
execute: () => {
console.log('Cut Cell(s)');
}
});

commands.addCommand('example:run-cell', {
label: 'Run Cell',
execute: () => {
console.log('Run Cell');
}
});

commands.addCommand('example:cell-test', {
label: 'Cell Test',
execute: () => {
console.log('Cell Test');
}
});

commands.addCommand('notebook:new', {
label: 'New Notebook',
execute: () => {
console.log('New Notebook');
}
});

commands.addKeyBinding({
keys: ['Accel X'],
selector: 'body',
command: 'example:cut'
});

commands.addKeyBinding({
keys: ['Accel C'],
selector: 'body',
command: 'example:copy'
});

commands.addKeyBinding({
keys: ['Accel V'],
selector: 'body',
command: 'example:paste'
});

commands.addKeyBinding({
keys: ['Accel J', 'Accel J'],
selector: 'body',
command: 'example:new-tab'
});

commands.addKeyBinding({
keys: ['Accel M'],
selector: 'body',
command: 'example:open-task-manager'
});

let palette = new CommandPalette({ commands });
palette.addItem({ command: 'example:cut', category: 'Edit' });
palette.addItem({ command: 'example:copy', category: 'Edit' });
palette.addItem({ command: 'example:paste', category: 'Edit' });
palette.addItem({ command: 'example:one', category: 'Number' });
palette.addItem({ command: 'example:two', category: 'Number' });
palette.addItem({ command: 'example:three', category: 'Number' });
palette.addItem({ command: 'example:four', category: 'Number' });
palette.addItem({ command: 'example:black', category: 'Number' });
palette.addItem({ command: 'example:new-tab', category: 'File' });
palette.addItem({ command: 'example:close-tab', category: 'File' });
palette.addItem({ command: 'example:save-on-exit', category: 'File' });
palette.addItem({
command: 'example:open-task-manager',
category: 'File'
});
palette.addItem({ command: 'example:close', category: 'File' });
palette.addItem({
command: 'example:clear-cell',
category: 'Notebook Cell Operations'
});
palette.addItem({
command: 'example:cut-cells',
category: 'Notebook Cell Operations'
});
palette.addItem({
command: 'example:run-cell',
category: 'Notebook Cell Operations'
});
palette.addItem({ command: 'example:cell-test', category: 'Console' });
palette.addItem({ command: 'notebook:new', category: 'Notebook' });
palette.id = 'palette';

// Search the command palette: update the inputNode, then force a refresh
palette.inputNode.value = 'clea';
palette.refresh();
MessageLoop.flush();

// Expect that headers and items appear in descending score order,
// even if the same header occurs multiple times.
const children = palette.contentNode.children;
expect(children.length).to.equal(7);
expect(children[0].textContent).to.equal('Notebook Cell Operations');
expect(children[1].getAttribute('data-command')).to.equal(
'example:clear-cell'
);
// The next match should be from a different category
expect(children[2].textContent).to.equal('File');
expect(children[3].getAttribute('data-command')).to.equal(
'example:close-tab'
);
// The next match should be the same as in a previous category
expect(children[4].textContent).to.equal('Notebook Cell Operations');
expect(children[5].getAttribute('data-command')).to.equal(
'example:cut-cells'
);
// The next match has the same category as the previous one did, so the header is not repeated
expect(children[6].getAttribute('data-command')).to.equal(
'example:run-cell'
);
});
});

describe('#handleEvent()', () => {
Expand Down

0 comments on commit d98765d

Please sign in to comment.