Closed VasanthakumarV closed 1 year ago
This is an awesome feature!
From the faq: The vim and nvim sessions seem to be handled by tpope's obsession.
We might want to use XDG_STATE_HOME
to store relevant data for this feature.
Should add that screen has this built in and might be something to look into.
One thing I'd like to add, which would be more or less the same feature, but more manual control:
Allow storing / loading sessions from/to file. That way, you could even start a session, store it somewhere in the cloud, then resume it (within reason) on a different machine, three days later.
I feel like, once session restoration after restart is working, this would probably be a no-brainer: Just expose the save/load functionality to the user, e.g. ctrl + o -> (s)ave session to file / (l)oad session from file
I second @TheSHEEEP's proposal. Having an ability to dump an existing session to a file would be great ! Also I think this feature would be worth being exposed from the command line too, e.g.
zellij --dump-session > dump.yml
Hi there! I am wondering if this feature was implemented already or is it still under development?
I think an MVP would be being able to restore all tabs, layouts, and each pane's current working directory. Restoring active processes like nvim sessions could be a follow on feature. Is this something that would be better implemented as a plugin?
I personally can not imagine myself using tmux without the session save/autoload. I love what you've achieved with Zellij and am considering to switching to it, but until there will be session saving/restoring - I can not switch for work, there's too many folders and panes and tabs to open each morning. I'll probably use Zellij for personal projects/off work coding. Session saving/restoring would be a killer feature if it's included out of the box, I'm sure Plugin would also work, as soon as there's session saving - I'm switching ;)
Just to chime in:
The implementer should figure out a way to turn this:
[ {x: 0, y: 0, width: 100, height: 50}, {x: 0, y: 50, height: 50, width: 50}, {x: 50, y: 50, height: 50, width: 50} ]
Into this:
layout {
pane
pane split_direction="vertical" {
pane
pane
}
}
The code base right now only does this the other way around. Similar implementations from other multiplexers - for the reason stated above - likely won't work. If someone wishes to take this up - awesome.
@imsnif let me try it!
@imsnif Do we need to save the session state to disk in layout
format though? What's stopping us from writing to disk exactly what you have ([ {x: 0, y: 0, width: 100, height: 50}, ...]
) and configuring session restoration to parse that?
Or are we hoping to use the layout
argument in start_client()
to achieve session restoration? If so, we'd have to parse the session state to the layout
format and write it to disk every time the layout changes (new split pane, new tab, rename something, etc...).
Is that the best way forward?
@sundy-li have you done any work on this?
@mike-lloyd03 - layout files is how we serialize our session state. While we could theoretically massage the entire code-base to add an additional serialization layer as you propose, it'll both be an incredible amount of work and an incredible lot to maintain.
It's logically possible (mostly) to perform this sort of serialization that I spoke about, and once we have that it'll be much cleaner to maintain and edit (you'd also be able to edit your saved layout/session files to fit other cases, for example).
So I essentially think this is the best way forward, yeah. :)
(There will probably be additional places where we need to update the session state to disk, eg. when opening a new app - which is something we cannot detect, so we'll probably end up doing this on every keystroke... but really, this is the easier part - once we have this serialization layer, we're home :) )
@imsnif Thank you. I'd like to at least take a whack at the algorithm for serializing structs into layouts. Can you provide me with a few more example cases (preferably edge cases).
And also is the example you gave above a vector of PaneGeom
s?
@mike-lloyd03 - sorry, I don't have a lot of time for doing back-and-forth on this. There are a lot of examples of layouts in the documentation, eg. here: https://zellij.dev/documentation/creating-a-layout.html
And yeah, my example assumed PaneGeom
. We of course don't keep things exactly like this, but as mentioned: once you get this basic unit to work we can start working on integrating it with everything else.
I think this is not started yet because it seems big. I'm going to start this with saving at least tab layout to a file and see how it goes.
@alekspickle Go for it. I started working on it a bit. It seems pretty straightforward since it's just an algorithm problem. I just haven't had time.
Layouts now support floating panes though so that might complicate it somewhat.
how can one save a setup that they want as a default? i do not want to save my setup but basically i want to have a default setup when i make a new session with zellij
i guess, i could create a layout that is loaded by default on a new session? 🤔
I am also curious if i can specify the default session by default in the configuration, so far i have not found that option
@eleijonmarck hijacking a thread is not nice. It is generally better to ask all help
questions in discord or create new issue that can be closed with an answer right away.
If you dump the config you can just specify the name of a default layout with default_layout name
If you want help with configuration to avoid reading corresponding docs part or the config itself, just ask in discord, people usually active there.
@alekspickle the question is related as I was asking for a current workaround. @alekspickle it's also generally not nice to tell someone they have hijacked a thread in open source software. we are all here to freely ask question. you are rather shying away potential users from using the software by that behavior.
@eleijonmarck , @alekspickle - I'm sure neither of you were meaning to be unkind or impolite to the other person. I ask you - let's please cool things down and enjoy our terminal emulators.
Any updates?
Hi, as I could not find any mention of progress on this issue, I decided to give it a try as practice. I am not new to programming but I am new to Rust and it was challenging! However I have managed to solve the "hard part" of the problem according to @imsnif, ie serialize the session geometry (Vec<PaneGeom>
) to a string; actually I did Vec<PaneGeom> -> Vec<TiledPaneLayout> -> String
.
Here is an example of geometry session to serialize:
[{"x": 0, "y": 0, "cols": 40, "rows": 30}, {"x": 0, "y": 30, "cols": 40, "rows": 30}, {"x": 40, "y": 0, "cols": 40, "rows":20}, {"x": 40, "y": 20, "cols": 20, "rows": 20}, {"x": 60, "y": 20, "cols": 20, "rows": 20}, {"x": 40, "y": 40, "cols": 40, "rows": 20}]
A representation of the layout so you don't have to scramble your brain trying to decipher:
+-----------------------+
| 1 | 3 |
| |-----------|
|-----------| 4 | 5 |
| 2 |-----------|
| | 6 |
+-----------------------+
The kdl string:
tab {
pane split_direction="vertical" {
pane size=40 {
pane size=30
pane size=30
}
pane size=40 {
pane size=20
pane size=20 split_direction="vertical" {
pane size=20
pane size=20
}
pane size=20
}
}
}
I did a few more examples in order to test the edge cases and I think I cover them all. As I said I am new to Rust but I think I managed to do clean code and it is suitable to integrate to the code base.
Could some contributors help me and/or guide me on what should be done next?
@AlixBernard Great work! I think some comments discouraged me to finish it.
You can start by opening a PR so the contributors can at least evaluate.
As a bonus points, do you think it covers new-ish swap_layout
s?
Hey @AlixBernard - this looks great! I'm looking forward to reading the algorithm.
So, the high level things we need to do to make this feature happen are:
I don't know how much of this you'd like to sign up for, but we can totally do it in chunks so that if you find you don't have the time or it's not fun for you, you won't lose the work you did. I find this feature to be high priority and so will try to make it a priority regarding guidance and such.
(also, forgot to mention: I'm happy to drill down into each one of these points with more specific pointers to places in the code base if you'd like)
As for infrastucture code needed for actions to work you can take a look at this draft
Thanks!
I think I should have specified that it is still really basic though, so far I only have a few functions that do the jobs (the main one being kdl_string_from_geoms
). It needs to be adapted to fit into the code base and to take into account everything else, I don't think it is advanced enough for a PR. I will add it below so you can judge by yourselves. I haven't investigated what are swap layouts so I assume they are not supported.
Here is some code with examples that can be put in zellij/zellij-utils/src/main.rs
and run with cd zellij/zellij-utils && cargo run
Here are some details on the algorithm implemented
I feel like I don't know enough the whole repo to know how to integrate it to a structure or make it as its own serialization module, if you could suggest an idea that will be helpful.
As you pointed out, there is still a lot to do and I don't have the time to do it all so anyone can feel free to use the algorithm or the code to do it. However I'm willing to try to improve this piece of code to allow flexible panes (I will need more info about this), work with swap layouts if possible, and adapt it so it can be properly integrated to the code base (please point to me anything that should be modified).
Anyway I'm happy to hear that this feature is considered a priority and I hope that my contribution will help its implementation. I'll try to help as I can)
I'll try to make a usable action out of this. There is a lot stuff to add, since layouts advanced quite a bit, but this is already good base. Huge effort, man!
Thanks to the two of you! I'm very happy to see this collab and things moving forward on this.
Just a note about swap layouts: I think it's safe to ignore them. They can't be changed at runtime and are handled as default in various cases, so we can probably deal with them on the "session restoration" side rather than the session serialization side we're discussing now.
I haven't delved deeply into the algorithm, but I think even this implementation brings us 80% of the way there. Please do reach out here or on Discord if you have any questions (eg. things that need to be supported or not, to save you time!)
Looking forward to this!
So I've been thinking about flexible panes and, if I understood correctly, they are simply PaneGeom
s that have Dimension
with Constraint::Percent(_)
instead of Constraint::Fixed(_)
for the attributes rows
and/or cols
, right?
It will add some complexity but I think it should be possible. However I would need to know exactly how the input would be and other details:
Vec<PaneGeom>
without extra information on the dimensions, min/max values, etc.?Dimension
has a constraint Constraint::Percent(_)
, then its inner
value will always be 1 or can it change? If it can change, in which way?Maybe giving an example would be easier than explaining everything, so below is an example of kdl layout output that covers some edge cases I foresee; if someone can give (or let me know how to obtain) the equivalent input I would get, it would help a lot. Thank you)
I tried to find the answers by myself but so far I'm still unsure and it is quite time consuming.
Hey @AlixBernard - first of all: please do ask. There's no need for you to spend time needlessly - the interaction between fixed and flexible panes in Zellij is also especially chaotic (split across a few different parts of the code base and written and patched by many different people).
So I've been thinking about flexible panes and, if I understood correctly, they are simply
PaneGeom
s that haveDimension
withConstraint::Percent(_)
instead ofConstraint::Fixed(_)
for the attributesrows
and/orcols
, right?
Yes, exactly.
* is it only a `Vec<PaneGeom>` without extra information on the dimensions, min/max values, etc.?
I'm not sure I understand the question... but I think in essence we're looking for an algorithm that does Vec<PaneGeom>
to stringified KDL layout. Everything else can be massaged into the logic later somewhere (eg. pane titles, commands and such). Makes sense, or did I misunderstand?
* if `Dimension` has a constraint `Constraint::Percent(_)`, then its `inner` value will always be 1 or can it change? If it can change, in which way?
I think the idea is that inner
is the "real" size of the pane (because pane sizes have to be translated to a fixed number of columns/rows so we know how to draw them). So if the screen width is 50 columns and we have a pane with 50%
, its inner would be 25
.
It's important to note though that we shouldn't rely on this always being the case. There are some intermediate states where this is not true (eg. in the middle of a resizing operation, on startup... but also in some unexpected places :)...)
Maybe giving an example would be easier than explaining everything,
Heh, nice edge case :) So, this is a good find and I think it's one of the reasons this is a bit of a hard problem. The "20%" pane in the layout you expressed means 20% out of its respective place in the tree, not in the whole of the screen (in the above case it would mean its direct neighbor gets 80%), but the data structures we hold mean 20% of the whole screen (since they don't know about the tree structure that originated them).
That being said, the vast majority of real-world user cases do not have such a complex setup. It's totally fine for this algorithm to fail with an error in all of these edge cases. If we can go for a "keep the fixed panes their size and divide the rest of the space as close as possible between the flexible panes" approach it would catch almost if not everything. Makes sense? Or did I totally misunderstand your question?
Alright, thank you for your reply, you answered most of my questions! Just two points to clarify:
inner
only little change would be required in the code, are you sure it is not an option?PaneGeom
object will only display 20% without specifying, right? If this is the case and we cannot rely on inner
then more tests will have to be performed but I think it should be possibleif we could rely on
inner
only little change would be required in the code, are you sure it is not an option?
Hum, in that case sure. We can probably work out any inconsistencies from the outside if they arise. But we do need the end result to be percentages (or preferably even just bare pane
nodes if it fits) unless the panes are explicitly fixed. Do you think it can work?
regarding the edge case, you mean that the 20% pane should have 20% of the (horizontal) size of its parent pane but the
PaneGeom
object will only display 20% without specifying, right? If this is the case and we cannot rely oninner
then more tests will have to be performed but I think it should be possible
Yes! But this is not a case you'd get as input. As input any percentage you get would be of the whole screen. And then you can make the translation to absolute size and then to percentage of its place in the tree once you build it. But I guess this is why using inner would be helpful, right?
Obtaining the percentage for the kdl string shouldn't be a problem, and omitting them when not required too. It is possible not to use inner
though this would imply more computation and a more convoluted code.
As input any percentage you get would be of the whole screen.
This should be fine as well but then what would happen when this is mixed with fixed panes? Are the fixed value always excluded, meaning that the sum of percentage from all panes that are "side by side" will always add up to 100%?
Yeah, the fixed panes are always excluded. Percentages should always add up to 100%.
Hey @AlixBernard - gentlest of pings. Do you think you will have time to work on this in the near future? No candidates to pick up the work so far, but I think it would be good to indicate either way so we're clear on where things stand.
I'd be super happy to wait for as long as needed if you want to see this through and need more time.
Hey! I should have more time from mid July to keep working on it. However, I would like to have some concrete test data (the PaneGeom
vector at least) as it would clear any doubts arising for the tricky parts and it would also allow me to make better tests directly. Let me know if you can provide it or, better yet, explain me how to get it myself.
Hey! I should have more time from mid July to keep working on it. However, I would like to have some concrete test data (the
PaneGeom
vector at least) as it would clear any doubts arising for the tricky parts and it would also allow me to make better tests directly. Let me know if you can provide it or, better yet, explain me how to get it myself.
Glad to hear it!
Here's a way to do it yourself:
pub fn debug_print_geoms(&self) {
log::info!("***** PRINTING PANE GEOMS *****");
for (pane_id, pane) in &self.panes {
log::info!("pane_id: {:?}, pane_geom: {:?}", pane_id, pane.position_and_size());
}
log::info!("*******************************");
}
set_pane_frames
function (https://github.com/zellij-org/zellij/blob/main/zellij-server/src/panes/tiled_panes/mod.rs#L288)
self.debug_print_geoms();
/tmp/zellij-1000/zellij-log/zellij.log
- I think there's more info abot it in the CONTRIBUTING.md doc (this should be triggered every time there's a change, eg. when opening a new pane)
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:115]: ***** PRINTING PANE GEOMS *****
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:117]: pane_id: Terminal(0), pane_geom: PaneGeom { x: 0, y: 1, rows: Dimension { constraint: Percent(100.0), inner: 17 }, cols: Dimension { constraint: Percent(50.0), inner: 117 }, is_stacked: false }
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:117]: pane_id: Terminal(1), pane_geom: PaneGeom { x: 117, y: 1, rows: Dimension { constraint: Percent(100.0), inner: 17 }, cols: Dimension { constraint: Percent(50.0), inner: 117 }, is_stacked: false }
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:117]: pane_id: Plugin(0), pane_geom: PaneGeom { x: 0, y: 0, rows: Dimension { constraint: Fixed(1), inner: 1 }, cols: Dimension { constraint: Percent(100.0), inner: 234 }, is_stacked: false }
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:117]: pane_id: Plugin(1), pane_geom: PaneGeom { x: 0, y: 18, rows: Dimension { constraint: Fixed(2), inner: 2 }, cols: Dimension { constraint: Percent(100.0), inner: 234 }, is_stacked: false }
INFO |zellij_server::panes::til| 2023-06-28 16:44:05.786 [screen ] [zellij-server/src/panes/tiled_panes/mod.rs:119]: *******************************
Please let me know if I can help with anything else!
Oh this would've saved me days in time and lots of confusion. goddammit 🥲
Haha yeah this was a blocker for me a few months ago as well.
@AlixBernard I added this to my draft, so you can experiment with it running zellij session, tailing zellij.log and running cargo xtask run -- action dump-layout
When saving geometry we should convert absolute coordinates to percentage so it can be resorted in window of any size
@mikem-zed it's kind of in the essense of a constraint
field I think :thinking:
I had to alter an algo a bit. It was not able to serialize the simplest layout so I doubled down to make this layout work first:
layout {
pane size=1 borderless=true {
plugin location="zellij:tab-bar"
}
pane
pane size=2 borderless=true {
plugin location="zellij:status-bar"
}
}
It outputs something obviously wrong:
HORIZONTAL:3
initial splits: [40], direction: Horizontal
VERTICAL:3
second step splits: [0, 37], direction: Vertical
@mikem-zed it's kind of in the essense of a
constraint
field I think thinking
yeah, I overlooked it while looking into the code.
@AlixBernard can you hint why switch direction after initial split number is <= 2
?
Yes! The get_splits_vertical
(and horizontal) functions maybe should be named get_edges_vertical
(and horizontal) as it actually gets the x
(or y
) coordinates of each edge/delimitation that spans the whole domain considered. Therefore, the edges of the domain are considered and there will always be at least two edges/delim found (<=
could be replaced by ==
). If after looking in one direction we only found 2 edges, it means either that there is only 1 pane in the domain (which is not possible as this possibility is taken care of before) or that the domain is split across the whole domain in the other direction (or that the domain is partitioned in a way that should not be possible).
Anyway, the last time I worked on this issue I made modifications to take care of flexible panes but haven't posted them here yet as it is not ready. I'm starting to look at it again but as I said I'll have more time from mid July so I don't know if I'll be able to post the new version until then.
Lastly I had come to the conclusion that for now it will be better to use the inner
attribute to figure out the layout but still taking into account the percentage so that flexible panes are serializable. Not using inner
might prevent the proper serialization of some cases but most of all I think it makes the code too convoluted for now and should be investigated later. Maybe a solution would be to create some sort of lock to prevent the change of the inner
values during serialization? This can be figured out once I have the code working
Hey @AlixBernard @alekspickle - I hope it's okay if I ask about progress? Anything I can do to help speed things along?
Hi, sorry for the late answer, I got busy and forgot about it..
Being able to see the real PaneGeom
s data from layouts cleared a lot of confusion for me as well, so some impossible cases can be ignored. I have working code with fixed panes but that should work with flexible panes too but couldn't test it much; having tests that can test dynamically would be better, would you be able to make them? Eg:
#[test]
fn layout_with_one_pane_serialize() {
let expected_kdl_layout = r#"
layout {
pane
}
"#;
let layout = Layout::from_kdl(expected_kdl_layout, "layout_file_name".into(), None, None).unwrap();
let geoms = geoms_from_layout(layout); // step to obtain the `PaneGeom`s of the layout
let kdl_layout = kdl_string_froms_geoms(geoms); // step serializing the layout into a kdl string
assert_eq!(kdl_layout, expected_kdl_layout);
}
It's more or less the same as the tests in zellij-utils/src/input/unit/layout_test.rs
There is also the problem of what other data should be added to the kdl string, such as the current working directory or the command/last command run (I don't know what is feasible).
For now, my implementation reconstruct a TiledPaneLayout
from a Vec<PaneGeom>
and use the TiledPaneLayout
to construct the kdl string which contains only information on the panes and their size.
I have some free time for the coming weeks so let me know if you want to discuss this more extensively via discord or an other way to speed up the process @imsnif @alekspickle
Currently, all the Zellij sessions are lost after the System restarts, it will be nice to have them restored for a seamless experience.
Reference