Skip to content

Conversation

@hsfzxjy
Copy link
Contributor

@hsfzxjy hsfzxjy commented Jul 5, 2023

Fix #179967.

This adds Ctrl+UpArrow and Ctrl+DownArrow navigation to

  • ExtensionsViewPaneContainer
  • KeybindingsEditor
  • CommentsPanel
  • MarkersView
  • Repl

The two keybindings are uniformly controlled by two new commands inputResults.focusResults and inputResults.focusInput.

@hsfzxjy hsfzxjy changed the title Generalize Ctrl+DownArrow and Ctrl+UpArrow to all input-result widgets (Fix #179967) Generalize Ctrl+DownArrow and Ctrl+UpArrow to most input-result widgets (Fix #179967) Jul 5, 2023
@meganrogge meganrogge added this to the July 2023 milestone Jul 5, 2023
@hsfzxjy hsfzxjy force-pushed the ctrl-arrow branch 4 times, most recently from b68e8ca to 5d64ccf Compare July 6, 2023 18:30
@meganrogge
Copy link
Contributor

Hey @hsfzxjy thanks so much for working on this. Something to consider - not sure if you have already thought of this - is are there pre-existing applications of this keybinding for each of the contexts?

IE in these views, does ctrl/cmd + up/downArrow have a function already that we will be overriding. I haven't gone through these yet individually, but that's what we'll need to do before proceeding with this so as to not break muscle memory.

for example, I adopted such a change in the search view - but did not realize that I broke muscle memory a bit since ctrl/cmd+downArrow when in the search in put box with multiline text should jump to the bottom of that, but currently instead jumps to the next input box.

@hsfzxjy
Copy link
Contributor Author

hsfzxjy commented Jul 6, 2023

@meganrogge Yes, I've investigated on this and ensure not breaking muscle memory.

The hardest part is that those Lists/Tables/Trees in the "results panel" by default use Ctrl+Up for scrolling up, which could conflict with the newly added action of focusing "input widget". I harmonize these two into:

  • If the List/Table/Tree is not scrolled at top most, Ctrl+Up will scroll it up as in the old version.
  • Otherwise, Ctrl+Up will focus corresponding "input widget", which is a new action compared with the old version.

Here's a list of behavior that this PR affects:

Extensions
Search box
Before: Ctrl+Down will focus extension list;
After: As-is.

Extension List
Before: Ctrl+Up will scroll up the extension list. If at top most position, nothing happens;
After: Ctrl+Up will still scroll up the extension list. If at top most position, focus the search box.


Keybinding Editor
Input box
Before: Ctrl+Down will focus keybinding table;
After: As-is.

Keybinding Table
Before: Ctrl+Up will scroll up the keybinding table. If at top most position, nothing happens;
After: Ctrl+Up will still scroll up the keybinding table. If at top most position, focus the input box.


MarkerView
Filter Box
Before: Ctrl+Down will focus view body;
After: As-is.

View Body
Before: Ctrl+Up will scroll up the view body. If at top most position, nothing happens;
After: Ctrl+Up will still scroll up the view body. If at top most position, focus the filter box.


CommentsView
Filter Box
Before: Ctrl+Down will focus the tree;
After: As-is.

Tree
Before: Ctrl+Up will scroll up the tree. If at top most position, nothing happens;
After: Ctrl+Up will still scroll up the tree. If at top most position, focus the filter box.


CommentsView
Filter Box
Before: nothing;
After: Ctrl+Down will focus the REPL Input.

REPL Input
Before: nothing;
After: Ctrl+Up will focus the filter box.


@meganrogge
Copy link
Contributor

Amazing 🙌 thank you. I'll review this next week.

@hsfzxjy hsfzxjy force-pushed the ctrl-arrow branch 5 times, most recently from 5664660 to 79caf94 Compare July 10, 2023 13:34
@sandy081
Copy link
Member

@meganrogge I see you are reviewing this PR. Is there anything you would need from me?

@hsfzxjy hsfzxjy force-pushed the ctrl-arrow branch 2 times, most recently from 679b20b to 7f73b97 Compare July 12, 2023 11:59
@meganrogge
Copy link
Contributor

@sandy081 a look at your particular area(s) would be good. Thanks

alexr00
alexr00 previously approved these changes Jul 12, 2023
Copy link
Member

@alexr00 alexr00 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commentsView.ts and commentsView.test.ts look good!

@meganrogge
Copy link
Contributor

@roblourens could you pls make sure it looks good for your area?

Comment on lines 143 to 175
function createScrollAtTopObserver(contextKeyService: IContextKeyService, widget: WorkbenchListWidget): IDisposable {
const listScrollAtTop = RawWorkbenchListScrollAtTopContextKey.bindTo(contextKeyService);
listScrollAtTop.set(widget.scrollTop === 0);
return widget.onDidScroll((e) => {
const shouldUpdate = (e.oldScrollTop === 0) !== (e.scrollTop === 0);
if (shouldUpdate) {
listScrollAtTop.set(e.scrollTop === 0);
}
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK there is no need to check for should update since that's taken care of further down the line.

Suggested change
function createScrollAtTopObserver(contextKeyService: IContextKeyService, widget: WorkbenchListWidget): IDisposable {
const listScrollAtTop = RawWorkbenchListScrollAtTopContextKey.bindTo(contextKeyService);
listScrollAtTop.set(widget.scrollTop === 0);
return widget.onDidScroll((e) => {
const shouldUpdate = (e.oldScrollTop === 0) !== (e.scrollTop === 0);
if (shouldUpdate) {
listScrollAtTop.set(e.scrollTop === 0);
}
});
}
function createScrollAtTopObserver(contextKeyService: IContextKeyService, widget: WorkbenchListWidget): IDisposable {
const listScrollAtTop = RawWorkbenchListScrollAtTopContextKey.bindTo(contextKeyService);
const update = () => listScrollAtTop.set(widget.scrollTop === 0);
update();
return widget.onDidScroll(update);
}

Comment on lines 84 to 87
private readonly _onDidBlur = this._register(new EventMultiplexer<void>());
readonly onDidBlur = this._onDidBlur.event;
private readonly _onDidFocus = this._register(new EventMultiplexer<void>());
readonly onDidFocus = this._onDidFocus.event;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of allocating additional objects, these two events should just be the same instances as the focusTracker's events. One suggestion: createInput could return the focusTracker instance along with the ContextScopedHistoryInputBox instance, then the events could simply be assigned in the constructor.

Comment on lines 306 to 307
this._register(this._onDidFocusResults.add(focusTracker.onDidFocus));
this._register(this._onDidBlurResults.add(focusTracker.onDidBlur));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is trickier to untangle, but should still be possible to do without any additional allocations. Let me know if you need pointers for that.

Comment on lines 694 to 695
this._register(this._onDidFocusResults.add(this.replInput.onDidFocusEditorWidget));
this._register(this._onDidBlurResults.add(this.replInput.onDidBlurEditorWidget));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same allocation comment as above.

register(view: IInputResultsView): IDisposable;
}

export class InputResultsService implements IInputResultsService {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a strange thing to become a service. First, I don't think we've ever defined such a concept as an input-result widget. But let's say we do... still, we don't have a class to encapsulate that widget's definition and behavior. We seem to be creating an abstract concept of a widget without creating the widget itself. 🤔

Copy link
Contributor Author

@hsfzxjy hsfzxjy Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I made it as a service so that we can define a unified command to make the navigation keybinding consistent across several widgets. Do you find it necessary to define unified commands for these navigation? If so, what's the better way for this?


Also after reading other comments you left, I agree that the term input-results is poorly defined and too constrained. For example in repl, it's hard to say which one of the filter box and the input box is the "input". Thus I feel like to rewrite this PR and extend it to more general cases -- use Ctrl+Up/Down to navigate among widgets in a vertical-layout container like the search panel did. This should come with the following advantages:

  1. No more confusion of "input" vs "results"
  2. Can be applied to container with more than two child widgets, instead of simple "input" and "results".

I am wondering what's your attitude to this proposal? Many thanks to your kind instructions.

Copy link
Contributor

@meganrogge meganrogge Jul 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you find it necessary to define unified commands for these navigation? If so, what's the better way for this?

We have what's called a MultiCommand - for example

export const AccessibilityHelpAction = registerCommand(new MultiCommand({
id: 'editor.action.accessibilityHelp',
precondition: undefined,
kbOpts: {
primary: KeyMod.Alt | KeyCode.F1,
weight: KeybindingWeight.WorkbenchContrib,
linux: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1,
secondary: [KeyMod.Alt | KeyCode.F1]
}
},
menuOpts: [{
menuId: MenuId.CommandPalette,
group: '',
title: localize('editor.action.accessibilityHelp', "Open Accessibility Help"),
order: 1
}],
}));

Your approach above sounds good to me, though I believe would still use the service concept?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have what's called a MultiCommand

Thanks for pointing out, never know this before.

I am actually rewriting this PR to realize general widget navigation in a container using Ctrl+Up/Down. With MultiCommand I think the service concept can be eliminated. But the time complexity of executing action may increase since there could be many containers attached to the action. Is it acceptable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds good. Thanks so much for working on this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meganrogge I have another idea to eliminate service, which I'm not sure is appropriate or not. I've put the pseudo code at https://gist.github.com/hsfzxjy/5beae21297024f443c2d69faffc4cfe5 . Could you check it and see whether this pattern is OK or why it's not acceptable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That LGTM

Comment on lines 123 to 125
private readonly _onDidBlurResults = this._register(new EventMultiplexer<void>());
private readonly _onDidFocusResults = this._register(new EventMultiplexer<void>());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same allocation comment.

@hsfzxjy
Copy link
Contributor Author

hsfzxjy commented Jul 15, 2023

I've completed a full rewrite on this PR in respond to @roblourens 's constructive suggestions. The changes at present are:

  1. Disable command list.scrollUp when scrollbar is at top-most position. Also, disable command list.scrollDown when scrollbar is at bottom-most position.
  2. Provide registerNavigatableContainer() function to enable Ctrl+Up/Down navigation among widgets in a container. Two new commands widgetNavigation.focusNext and widgetNaviagation.focusPrevious are added.
  3. Enable such widget navigation under more cases, including ExtensionsViewContainer, KeybindingsEditor, Repl, CommentsPanel, MarkersView and TestingExplorerView.

@meganrogge I think we should start a new review? Sorry about the occupation of your precious time. Thanks! 🙏🏼

@meganrogge
Copy link
Contributor

meganrogge commented Jul 17, 2023

@hsfzxjy thank you so much for your continued work on this. based on my testing, things are working very well 🎉

I'm seeing this error though:

Error: vs/platform/list/browser/listService.ts(296,69): error TS2345: Argument of type 'this' is not assignable to parameter of type '{ readonly onDidScroll: Event<any>; readonly scrollTop: number; readonly scrollHeight: number; readonly renderHeight: number; }'.
  Type 'WorkbenchList<T>' is missing the following properties from type '{ readonly onDidScroll: Event<any>; readonly scrollTop: number; readonly scrollHeight: number; readonly renderHeight: number; }': onDidScroll, renderHeight

EDIT:
I am not seeing that error when I compile locally

@hsfzxjy
Copy link
Contributor Author

hsfzxjy commented Jul 17, 2023

@meganrogge Saw this. I am troubleshooting it.

@hsfzxjy
Copy link
Contributor Author

hsfzxjy commented Jul 17, 2023

The error is gone now. It was caused by an overly aggressive tree-shaking.

@meganrogge
Copy link
Contributor

@joaomoreno if you want to give this another pass, I plan to test and approve today if all works well

Copy link
Contributor

@meganrogge meganrogge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏🏼

@meganrogge meganrogge enabled auto-merge July 18, 2023 15:33
@meganrogge meganrogge merged commit a9fcc43 into microsoft:main Jul 18, 2023
@meganrogge meganrogge mentioned this pull request Jul 18, 2023
@hsfzxjy hsfzxjy deleted the ctrl-arrow branch July 19, 2023 06:09
@github-actions github-actions bot locked and limited conversation to collaborators Sep 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accessibility: Generalize Ctrl+DownArrow and Ctrl+UpArrow to all input-result widgets

6 participants