-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Initial version of previous active tab toggle. #4296
Conversation
…n't seem to work.
…o tab-to-last-modified
const commandID = 'application:activate-previous-active-tab'; | ||
if (!commands.hasCommand(commandID)) { | ||
commands.addCommand(commandID, { | ||
label: 'Activate Previous Active Tab', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this label is kind of confusing. Perhaps something like "Activate Previously Used Tab"?
if (!commands.hasCommand(commandID)) { | ||
commands.addCommand(commandID, { | ||
label: 'Activate Previous Active Tab', | ||
execute: () => app.commands.execute(`tabmenu:activate-${previousId}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can check if previousId
exists here. Something like
execute: () => previousId && app.commands.execute(`tabmenu:activate-${previousId}`)
This is looking great @siryog90! To address your specific questions: Notes
Questions
|
@ian-r-rose Ok, so the command works fine, but here are a couple things I am not particularly fond of. Issue 1Say I open tab 1 (notebook), and then tab 2 (notebook). Now I can toggle between them. Now suppose I double click on some file that JupyterLab is unable to open. Then Because of this, checking for Issue 2To your point, "We should probably do a check there as well for whether a widget has been closed," I can use the |
Hmm, I am not sure the best way to approach issue 1 at the moment, it is a tricky one. Let me think on that. |
One possibility for your first issue (though it is a bit indirect, and I know that the API for this is going to change soon-ish): you an check if the new widget is a document, then only update let widget = args.newValue;
if (!widget) {
return;
}
let context = docmanager.contextForWidget(widget); // If the widget is a document, this will succeed.
if (context) {
context.ready.then(() => {
previousWidget = widget;
});
} else {
previousWidget = widget;
} |
Let's look at what other IDEs use for this keyboard shortcut as well. |
Also, we should use probably prefer to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@siryog90 I think we should move forward with this. The issue with failed opening of a tab is an edge case that we would like to fix, but it can be done as a follow-up.
Can you change Ctrl
to Accel
to be more OS-independent? Also, it would be nice to know if there are any other applications that use '
as a keyboard shortcut for this (if we can't find any points of comparison, I have no problem with the choice).
if (!commands.hasCommand(CommandIDs.activatePreviouslyUsedTab)) { | ||
commands.addCommand(CommandIDs.activatePreviouslyUsedTab, { | ||
label: 'Activate Previously Used Tab', | ||
execute: () => previousId && app.commands.execute(`tabmenu:activate-${previousId}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It occurs to me that we can also add an isEnabled
function here, so that the menu item is greyed out when previousId
is not set. Something like
isEnabled: () => !!previousId
@ian-r-rose Give it a go when you get a chance, and let me know what you think. Regarding other software keyboard shortcuts. First thought is to check browsers. Chrome has an extension that provides the functionality. It uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@siryog90 Just took the most recent version out for a spin, and it works great! I have a couple of small suggestions, and then I think we should merge.
|
||
// Command to toggle between the current | ||
// tab and the previously used tab. | ||
if (!commands.hasCommand(CommandIDs.activatePreviouslyUsedTab)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if
statement check is not necessary, and I think it can be safely removed, since the command will only be added once on plugin activation.
if (!commands.hasCommand(CommandIDs.activatePreviouslyUsedTab)) { | ||
commands.addCommand(CommandIDs.activatePreviouslyUsedTab, { | ||
label: 'Activate Previously Used Tab', | ||
execute: () => previousId && app.commands.execute(`tabmenu:activate-${previousId}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also grey out the menu item when there is no previously used tab by adding an isEnabled
property here. Something like
isEnabled: () => !!previousId
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding isEnabled
, it would be nice to disable the command if the user manually 'x'-s out the previously used tab. I am wondering if there is an event that fires in such a scenario. I would then check if the id
of that widget matches previousId
and null out the latter.
The following works.
const populateTabs = () => {
menu.removeGroup(tabGroup);
tabGroup.length = 0;
let widgetDisposal = function (widget: Widget) {
if (widget && widget.id === previousId) {
previousId = '';
}
};
each(app.shell.widgets('main'), widget => {
widget.disposed.connect(widgetDisposal);
tabGroup.push(createMenuItem(widget));
});
menu.addGroup(tabGroup, 1);
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a nice idea. We could probably do it with a disposed
signal, as you suggest (there is no problem with more than one connection to a given signal). I wonder if it would be easier to scan through the widgets in the main area to see if they are still there. Something like
isEnabled: () => previousId && find(app.shell.widgets('main), widget => widget.id === previousId )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, since populateTabs
is called every time that the layout is modified (including when tabs are closed), the find
function I described could also do the job of clearing out the previousId
string there. So it could be
const populateTabs = () => {
menu.removeGroup(tabGroup);
tabGroup.length = 0;
each(app.shell.widgets('main'), widget => {
tabGroup.push(createMenuItem(widget));
});
if (!find(app.shell.widgets('main'), widget => widget.id === previousId) {
previousId = '';
}
menu.addGroup(tabGroup, 1);
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the alternative approach. One wouldn't necessarily expect people to have a ton of tabs open at one time, but are we o.k. with the possible time hit find
introduces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be okay. In fact, we are already iterating over the main area widgets, so that check can even go into the each
function above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const populateTabs = () => {
menu.removeGroup(tabGroup);
tabGroup.length = 0;
let isPreviouslyUsedTabAttached = false;
each(app.shell.widgets('main'), widget => {
if (widget.id === previousId) {
isPreviouslyUsedTabAttached = true;
}
tabGroup.push(createMenuItem(widget));
});
menu.addGroup(tabGroup, 1);
previousId = isPreviouslyUsedTabAttached ? previousId : '';
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that would work nicely, I think. And it only involves iterating over the widgets once.
I don't think we need to worry about the error tabs for now. |
I think it is ready for a spin. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great @siryog90, thanks for the hard work!
Thanks! |
@ian-r-rose Thanks for the guidance! |
@ian-r-rose @siryog90 I'm on mac OS, tried also CMD + Shift and still, not working. I'm on version 0.32.1, is it suppose to be there? Thanks ;) |
@idoDavid The feature is not released yet. |
@siryog90 I'm on 0.33.4 - |
@ian-r-rose @NoahStapp @blink1073 |
Thanks for tracking that down @siryog90, I think that removal was probably a mistake. I can submit a PR to reinstate it. |
#4177
Here is an initial version of the functionality. Default keyboard shortcut is Ctrl Shift '
Notes
If you never open a second tab,
previousID
will remain the empty string, and the command will throw an error. Not sure if I should handle this, as a few commands throw errors at application launch time, but none are critical.Suppose we have three tabs open, and the command toggles between tab 1 and tab 3. Now, say you close tab 1. The command will essentially not do anything until we click on another tab, since the widget is no longer in the tab bar.
Question
In all of the menu creation functions
menu.menu.commands
is used with the exception ofcreateTabsMenu
. Here,app.commands
is used. Why is this?The
commandID
declaration is increateTabsMenu
in stead of theCommandIDs
namespace. Should I change the command totabmenu
level in stead ofapplication
level (likeactivate-next-tab
) and move the declaration toCommandIDs
?Should I add the command to the palette?
It would be useful to check that the tab we are trying to toggle is current docked (i.e. We are not trying to switch to some temporary widget that was unable to load some unsupported file format). Can I use
isVisible
for this?