Skip to content

Commit

Permalink
Action Widget - removed custom keyboard controller, added support in …
Browse files Browse the repository at this point in the history
…list widget (microsoft#190765)

* added fix to custom keyboard controller

* removed custom keyboard controller, using list widget keyboard label provider

* code cleanup
  • Loading branch information
justschen authored Aug 22, 2023
1 parent cf4f089 commit 620eb95
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 54 deletions.
29 changes: 23 additions & 6 deletions src/vs/base/browser/ui/list/listWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { timeout } from 'vs/base/common/async';
import { Color } from 'vs/base/common/color';
import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
import { matchesPrefix } from 'vs/base/common/filters';
import { matchesFuzzy2, matchesPrefix } from 'vs/base/common/filters';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
Expand Down Expand Up @@ -527,11 +527,27 @@ class TypeNavigationController<T> implements IDisposable {
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(this.view.element(index));
const labelStr = label && label.toString();

if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
this.previouslyFocused = start;
this.list.setFocus([index]);
this.list.reveal(index);
return;
if (this.list.options.typeNavigationEnabled) {
if (typeof labelStr !== 'undefined') {
const prefix = matchesPrefix(word, labelStr);
const fuzzy = matchesFuzzy2(word, labelStr);

// ensures that when fuzzy matching, it doesn't clash with prefix matching (1 input vs 1+ should be prefix and fuzzy respecitvely)
const fuzzyScore = fuzzy ? fuzzy[0].end - fuzzy[0].start : 0;
if (prefix || fuzzyScore > 1) {
this.previouslyFocused = start;
this.list.setFocus([index]);
this.list.reveal(index);
return;
}
}
} else {
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
this.previouslyFocused = start;
this.list.setFocus([index]);
this.list.reveal(index);
return;
}
}
}
}
Expand Down Expand Up @@ -978,6 +994,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider<T>;
readonly keyboardNavigationDelegate?: IKeyboardNavigationDelegate;
readonly keyboardSupport?: boolean;
readonly keyboardNavigationEnabled?: boolean;
readonly multipleSelectionController?: IMultipleSelectionController<T>;
readonly styleController?: (suffix: string) => IStyleController;
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
Expand Down
60 changes: 12 additions & 48 deletions src/vs/platform/actionWidget/browser/actionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom';
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { matchesFuzzy2 } from 'vs/base/common/filters';
import { Codicon } from 'vs/base/common/codicons';
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
import { Disposable } from 'vs/base/common/lifecycle';
Expand Down Expand Up @@ -44,11 +43,6 @@ interface IActionMenuTemplateData {
readonly keybinding: KeybindingLabel;
}

interface IActionFilteredItems {
readonly label: string | undefined;
readonly index: number;
}

export const enum ActionListItemKind {
Action = 'action',
Header = 'header'
Expand Down Expand Up @@ -154,6 +148,15 @@ class AcceptSelectedEvent extends UIEvent {
class PreviewSelectedEvent extends UIEvent {
constructor() { super('previewSelectedAction'); }
}

function getKeyboardNavigationLabel<T>(item: IActionListItem<T>): string | undefined {
// Filter out header vs. action
if (item.kind === 'action') {
return item.label;
}
return undefined;
}

export class ActionList<T> extends Disposable {

public readonly domNode: HTMLElement;
Expand All @@ -164,10 +167,6 @@ export class ActionList<T> extends Disposable {
private readonly _headerLineHeight = 26;

private readonly _allMenuItems: readonly IActionListItem<T>[];
private readonly _allMenuItemsFiltered: IActionFilteredItems[] = [];

private keypresses: string[] = [];
private timeout: number | null = null;

constructor(
user: string,
Expand All @@ -191,6 +190,8 @@ export class ActionList<T> extends Disposable {
new HeaderRenderer(),
], {
keyboardSupport: false,
typeNavigationEnabled: true,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel },
accessibilityProvider: {
getAriaLabel: element => {
if (element.kind === ActionListItemKind.Action) {
Expand All @@ -207,18 +208,15 @@ export class ActionList<T> extends Disposable {
getWidgetRole: () => 'listbox',
},
}));

this._list.style(defaultListStyles);

this._register(this._list.onMouseClick(e => this.onListClick(e)));
this._register(this._list.onMouseOver(e => this.onListHover(e)));
this._register(this._list.onDidChangeFocus(() => this._list.domFocus()));
this._register(this._list.onDidChangeSelection(e => this.onListSelection(e)));
this._register(this._list.onKeyPress(e => this.onKeyPress(e)));

this._allMenuItems = items;
const menuItems = this._allMenuItems;
this._allMenuItemsFiltered = menuItems.flatMap((obj, index) => (obj.kind === `action` && !obj.disabled) ? [{ label: obj.label, index }] : []);

this._list.splice(0, this._list.length, this._allMenuItems);

if (this._list.length) {
Expand Down Expand Up @@ -313,40 +311,6 @@ export class ActionList<T> extends Disposable {
this._list.setFocus([]);
}
}

private onKeyPress(e: KeyboardEvent) {
this.keypresses.push(e.key);
this.listFuzzyMatch(this.keypresses.join(''));

// Clear the previous timeout (if any)
if (this.timeout !== null) {
window.clearTimeout(this.timeout);
}

// Set a new timeout
this.timeout = window.setTimeout(() => { this.processKeyPresses(); }, 1000);

}

private processKeyPresses() {
this.keypresses = [];
}

private listFuzzyMatch(str: string) {
for (const menuItem of this._allMenuItemsFiltered) {
if (menuItem.label) {
const result = matchesFuzzy2(str, menuItem.label);
// Result is either null or an array of obj {start, end} for matches between str and menuItem.label
if (result) {
this._list.setFocus([menuItem.index]);
if (result[0].start === 0) {
// Breaks when finds first found instance of match [0,1+] to [0,1+]
break;
}
}
}
}
}
}

function stripNewlines(str: string): string {
Expand Down

0 comments on commit 620eb95

Please sign in to comment.