-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Add ability to pin tabs #9614
Add ability to pin tabs #9614
Conversation
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.
@sh7dm are you planning on updating the UI for pinned tabs as well, to visually represent that they are pinned? At the moment there is no distinction between pinned tabs and regular tabs.
For example, in the issue and in vscode we have (first tab is pinned):
@vince-fugnitto hello, I plan for sure. Is |
@sh7dm I believe it would be a fine solution, for example it is used when setting a dirty decoration in editors: theia/packages/core/src/browser/saveable.ts Lines 207 to 217 in 4c9b8b8
|
One more feature: keyboard shortcut. Is it that simple like registering both on the same shortcuts since those commands are mutually exclusive? |
It should be, you can try (and set it to the same value found in vscode). |
No, that didn't work :( . I changed unpin to |
So, I think I can merge those two commands into one to save space in menu + give users a single keybinding? |
@sh7dm I'm not sure I follow? In vscode there are two commands present: export const PIN_EDITOR_COMMAND_ID = 'workbench.action.pinEditor';
export const UNPIN_EDITOR_COMMAND_ID = 'workbench.action.unpinEditor'; I do not think we should merge the commands, rather their enablement should be controlled by if the editor is currently pinned or not. |
Sadly, those couldn't coexist with the same keybinding (see my comment) |
They can, just need a proper |
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.
A couple of observations with the pull-request:
- Should the functionality be specific to editors-widgets only?
- What behavior do we expect for pinned editors in the sidebar (not main-area), they are currently not properly decorated.
- Clicking the pin icon of a pinned editor does not unpin it like in VS Code, any plans to align?
- Pinning multiple views in the sidebar re-arranges them in an order that it is unexpected.
In general, perhaps we can simplify to only pinning editors, and only pinning them in the main-area.
I did not really notice weird ordering in sidebar. @vince-fugnitto could you please describe reproduction steps? |
I could no longer reproduce it, I believe part of the confusion is due to side-panels not being decorated when pinned. |
@sh7dm I'm not really sure how it should look, we already have some issues in sidepanels for editors when changing file-icon themes. I would not hold back the pull-request if the initial changes does not add decorations. @colin-grant-work any thoughts? |
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 is looking pretty good to me. One small point is that in VSCode, when you pin an editor preview widget, it converts to a normal widget, but that isn't happening here.
If you'd like to implement that for editor previews, you can modify the initializePreview
method in editor-preview-widget.ts
to look like this:
initializePreview(): void {
const oneTimeListeners = new DisposableCollection();
this._isPreview = true;
this.title.className += ` ${PREVIEW_TITLE_CLASS}`;
const oneTimeDirtyChangeListener = this.saveable.onDirtyChanged(() => {
this.convertToNonPreview();
oneTimeListeners.dispose();
});
oneTimeListeners.push(oneTimeDirtyChangeListener);
const oneTimeTitleChangeHandler = () => {
if ((this.title as any).pinned) {
this.convertToNonPreview();
oneTimeListeners.dispose();
}
};
this.title.changed.connect(oneTimeTitleChangeHandler);
oneTimeListeners.push(Disposable.create(() => this.title.changed.disconnect(oneTimeTitleChangeHandler)));
this.toDispose.push(oneTimeListeners);
}
If you're interested in implementing the unpin-on-click behavior, one way to do it would be to modify the rendering here to react to pinned / unpinned status. Another strategy would be do something like this:
document.addEventListener('dblclick', this.convertEditorOnDoubleClick.bind(this)); |
Where you check whether the click is on an element with the class name you're interested in and, if so, execute the unpin command. But I'm fine leaving that out, for now.
I agree with this. Figuring out how to handle the decoration on sidebars will be a bit tricky, but I also think it isn't urgent because pinning in that context is going to be much less common than pinning in the main area, so we don't have to resolve it immediately. |
Build failure is not caused by changes, there's some network or server error. https://github.com/eclipse-theia/theia/runs/2902667623#step:5:1446 |
@sh7dm this is working very well for me. I'm not seeing any problems on the main tabbar or other tabbars. Personally, I don't really like the tab rearrangement behavior, but that is how VSCode is doing it, and this code implements it correctly. On the code side, I think I'm going to backpedal, with apologies, and suggest that the Otherwise the code looks good to me. @vince-fugnitto, are you comfortable with the changes to the tabbar code introduced by this commit? They look reasonable to me, but it's digging pretty deep into |
Why clone VSCode behavior in terms of UX? Theia is a different project with its own UX, we can implement better behavior I think. If possible, suggest your ideas for rearrangement.
|
A fair question. The answer (not a very good one) is that it's at least a baseline to target, and users familiar with VSCode will have a lower learning / surprise curve if we stick closely to the patterns VSCode establishes. I suppose the arguments run as follows: For rearranging
Against rearranging
My preference would be to just decouple pinning and arrangement, since they're conceptually separate. You can pin a tab if you don't want it to close (I'm a habitual
True, and I won't insist on reverting to |
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.
@sh7dm do we plan on supporting the following use-case? (supported in vscode)
- pin an editor
- reload the application
- the editor is restored, but not its pinned state
I'd like to do so. How to include some extra data to save of application state? |
In order to store and restore state, |
Perhaps this is a case where the |
You're right, we should save layout and not widget state. So we should take a look at |
Thanks, will do! |
Signed-off-by: Dmitry Sharshakov <[email protected]>
Signed-off-by: Dmitry Sharshakov <[email protected]>
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.
Please be sure to clean up the commits, it seems the ECA check is failing.
I fixed ECA by going to the website, now it's all-ok. |
Signed-off-by: Dmitry Sharshakov <[email protected]>
Signed-off-by: Dmitry Sharshakov <[email protected]>
Signed-off-by: Dmitry Sharshakov <[email protected]>
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'm not sure about how you're restoring the pinned state on reload, mainly about why its only being done to main and bottom and not in a general way, I'll need some time to think about it.
@colin-grant-work I'd appreciate if you can take a look and provide any feedback if you have any.
@vince-fugnitto Side panels are done in their generic code, but I couldn't find a good generic place for it in main/bottom. |
const pinned: boolean[] = []; | ||
|
||
toArray(this.mainPanel.widgets()).forEach((a, i) => { | ||
pinned[i] = a.title.className.indexOf('theia-mod-pinned') >= 0 | ||
}); |
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.
maybe just this:
const pinned = toArray(this.mainPanel.widgets()).map(widget => widget.title.className.includes('theia-mod-pinned');
Alternatively, since there's some uncertainty below about whether the number of widgets examined here will be the same as the number of widgets eventually restored, you could use something like WidgetManager.getDescription()
to get a (probably) unique string(ifiable) identifier for each widget, use that as the key in a Record<string, boolean>
and then match that up with the restored widgets on the other side of the restoration.
@@ -638,6 +645,14 @@ export class ApplicationShell extends Widget { | |||
if (mainPanel) { | |||
this.mainPanel.restoreLayout(mainPanel); | |||
this.registerWithFocusTracker(mainPanel.main); | |||
const widgets = toArray(this.mainPanel.widgets()); | |||
if (mainPanelPinned && mainPanelPinned.length === widgets.length) { |
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.
See above for a way to handle the uncertainty of whether the widgets restored will be the same as the widgets whose state was saved.
getPinnedBottomWidgets(): boolean[] { | ||
const pinned: boolean[] = []; | ||
|
||
toArray(this.bottomPanel.widgets()).forEach((a, i) => { | ||
pinned[i] = a.title.className.indexOf('theia-mod-pinned') >= 0; | ||
}); | ||
|
||
return pinned; | ||
} |
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.
Not sure that this warrants its own function. Maybe getPinnedWidgetsForArea(area)
that can handle both of them?
@@ -643,6 +655,15 @@ export class ApplicationShell extends Widget { | |||
} else { | |||
this.collapseBottomPanel(); | |||
} | |||
const widgets = toArray(this.bottomPanel.widgets()); | |||
if (bottomPanel.pinned && bottomPanel.pinned.length === widgets.length) { |
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.
Same comment re: handling uncertainty of restoration.
The restoration on sidebars is a little unreliable for me. The following scenario seems to fail consistently.
If I move it into the main area and back, then it seems to retain its pinned state even on the sidebar. But if I close and reopen, leave it pinned, close and reopen again, it will have lost its pinned state. Since the only check for pinning is the classname, I'd guess the sidebar is wiping out the classname at some point. On the other hand, an editor on the sidebar seems to retain its pinned state more reliably, so there may be more to it - something related to Anyway - tl;dr is that I didn't have any problems in main or bottom areas, but the side panels were a little funky. |
Actually, I once noticed view self-unpinning after opening and closing it (switching side panel views). Maybe they are recreated on some conditions? |
Btw pinning side panel views is not really used feature, typically they do not need to be protected from being accidentally closed. |
@sh7dm, are you interested in further work on this feature? |
Not really sure, we should plan required changes |
@sh7dm, I think (apart from the conflicts that have arisen in the meantime), the main thing that was holding it back was the side-panel state restoration behavior. I agree with you that that isn't a very common use case, but we should decide whether to just disable it completely in side panels, or we should figure out what's making it buggy. If you're willing to investigate, I'd be happy to take a look at what you find; if you don't have time, I'd be happy to pick up the PR and do some investigation. |
Most likely I won't be able to properly debug this as I couldn't do that. Sorry for this problem. You can probably disable it for initial merge and open an issue for that. |
What it does
Commands for pinning and unpinning tabs. Fixes #9326.
How to test
Open multiple editors, pin and unpin some of those
Review checklist
Reminder for reviewers