Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interleaves search results across categories #706

Merged
merged 5 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading