Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focu
const SETTINGS_EDITOR_COMMAND_SHOW_PREVIOUS_SEARCH = 'settings.action.showPreviousSearch';
const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH_ON_ENTER = 'settings.action.focusSettingsFromSearchOnEnter';
const SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS = 'settings.action.focusSearchFromSettings';
const SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING = 'settings.action.focusNextSetting';
const SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING = 'settings.action.focusPreviousSetting';
const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList';
const SETTINGS_EDITOR_COMMAND_FOCUS_TOC = 'settings.action.focusTOC';
const SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL = 'settings.action.focusSettingControl';
Expand Down Expand Up @@ -742,6 +744,50 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
}
}));

this._register(registerAction2(class extends Action2 {
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING,
precondition: CONTEXT_SETTINGS_EDITOR,
keybinding: {
primary: KeyCode.F7,
weight: KeybindingWeight.WorkbenchContrib,
when: null
},
f1: true,
category,
title: nls.localize2('settings.focusNextSetting', "Focus Next Setting")
});
}

run(accessor: ServicesAccessor): void {
const preferencesEditor = getPreferencesEditor(accessor);
preferencesEditor?.focusNextSetting();
}
}));

this._register(registerAction2(class extends Action2 {
constructor() {
super({
id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING,
precondition: CONTEXT_SETTINGS_EDITOR,
keybinding: {
primary: KeyMod.Shift | KeyCode.F7,
weight: KeybindingWeight.WorkbenchContrib,
when: null
},
f1: true,
category,
title: nls.localize2('settings.focusPreviousSetting', "Focus Previous Setting")
});
}

run(accessor: ServicesAccessor): void {
const preferencesEditor = getPreferencesEditor(accessor);
preferencesEditor?.focusPreviousSetting();
}
}));

this._register(registerAction2(class extends Action2 {
constructor() {
super({
Expand Down
48 changes: 47 additions & 1 deletion src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,52 @@ export class SettingsEditor2 extends EditorPane {
this.tocTree.domFocus();
}

/**
* Moves focus to the next setting's control, skipping group headers. Works no matter
* whether focus is currently in the search input, on a tree row, or inside a setting's
* control. Stops at the last setting.
*/
focusNextSetting(): void {
this.focusAdjacentSetting(true);
}

/**
* Like {@link focusNextSetting}, but backwards. Stops at the first setting.
*/
focusPreviousSetting(): void {
this.focusAdjacentSetting(false);
}

private focusAdjacentSetting(next: boolean): void {
if (!this.isSearchUpToDate()) {
return;
}

// The tree's focus tracks the setting whose control contains DOM focus, so the anchor is valid even while a control is focused.
const anchor = this._currentFocusContext === SettingsFocusContext.SettingTree || this._currentFocusContext === SettingsFocusContext.SettingControl
? this.settingsTree.getFocus()[0]
: undefined;
const navigator = this.settingsTree.navigate(anchor);
let target = !anchor && !next ? navigator.last() : next ? navigator.next() : navigator.previous();
while (target && !(target instanceof SettingsTreeSettingElement)) {
target = next ? navigator.next() : navigator.previous();
}
if (!(target instanceof SettingsTreeSettingElement)) {
return;
}

// Reveal renders the row synchronously so its control can be focused below.
this.settingsTree.reveal(target);
this.settingsTree.setFocus([target]);
// The row selector avoids matching the previously focused setting, whose container
// keeps a stale focused class until DOM focus moves away from its control.
// eslint-disable-next-line no-restricted-syntax
const control = this.settingsTree.getHTMLElement().querySelector(`.monaco-list-row.focused ${AbstractSettingRenderer.CONTROL_SELECTOR}`);
if (control) {
(<HTMLElement>control).focus();
}
}

/**
* Invoked when the user presses the down arrow while the search input is focused.
* Navigates forward through the search history first; only once there are no more
Expand Down Expand Up @@ -705,7 +751,7 @@ export class SettingsEditor2 extends EditorPane {
* focus is not moved into stale results.
*/
private isSearchUpToDate(): boolean {
return !this.searchInputDelayer.isTriggered && this.renderedSearchQuery === this.searchWidget.getValue().trim();
return !this.searchInputDelayer.isTriggered() && this.renderedSearchQuery === this.searchWidget.getValue().trim();
}

/**
Expand Down