cdepillabout / termonad

Terminal emulator configurable in Haskell.
https://hackage.haskell.org/package/termonad
BSD 3-Clause "New" or "Revised" License
401 stars 49 forks source link

split panes #205

Open gelisam opened 3 years ago

gelisam commented 3 years ago

One feature I really like from iTerm2 is split panes. In particular, I always split my tabs vertically, with my text editor in the left pane, and ghcid in the right pane. I can currently achieve this with two termonad windows side-by-side, but this breaks down when I have multiple tabs, as I would like switching to a different tab to switch both to the source code of the new tab and to the ghcid of the new tab.

How would I go about implementing this?

cdepillabout commented 3 years ago

Do you know of a terminal emulator on Linux that has this same functionality? I'd like to play around with it to get a feel for how it works.

Of course, the easiest way to do what you want to do would be to just use a terminal multiplexer (like tmux or screen) inside a single Termonad tab, but I'm assuming you don't want to do this.


Currently Termonad just has a single gi-vte Terminal inside a gtk Notebook page.

The code for creating a new Terminal and Notebook tab is here: https://github.com/cdepillabout/termonad/blob/d817fe47139d3b39a9c4ec1e4f27ed419214d3b3/src/Termonad/Term.hs#L442-L459

If you wanted to add functionality for having multiple Terminals inside a single Notebook tab, I imagine you'd have to edit the above file to add a function (or modify the linked function) to not add a new Notebook tab, but instead optionally split an existing Notebook tab and add a new Terminal to it.

I imagine GTK provides some sort of widget to allow you to arbitrarily split a layout in half, but I'm not sure what would be the best widget to use. Maybe try taking a look at the following:

There are probably also a few places in the code that make an assumption that there is a one-to-one mapping between Notebook tabs and Terminals. I could help look for these once you have something basically working here.

There are a few other small things you'll have to decide while working on this. The first thing that comes to mind is what to put in the Notebook tab label when you have multiple panes. With a single terminal in a Notebook tab, you can just have either the name of the process running in the terminal (or let the terminal set the title), but with multiple tabs you'll have to decide what makes the most sense here.

You'll likely also need to add a new key command for opening a new pane instead of a new tab. Here's where the New Tab command is defined:

cdepillabout commented 3 years ago

There is also a long-standing issue about the way Termonad makes the Terminal scrollable: https://github.com/cdepillabout/termonad/issues/114. The refactoring required to fix this issue (#205) might be a good chance to fix #114 as well.

gelisam commented 3 years ago

Do you know of a terminal emulator on Linux that has this same functionality?

I found one, Konsole.

Of course, the easiest way to do what you want to do would be to just use a terminal multiplexer (like tmux or screen) inside a single Termonad tab, but I'm assuming you don't want to do this.

Yes, that's my fallback plan if adding this feature to Termonad turns out to be too complicated. Thanks for the code pointers, I'll give it a shot!

gelisam commented 3 years ago

I imagine GTK provides some sort of widget to allow you to arbitrarily split a layout in half, but I'm not sure what would be the best widget to use. Maybe try taking a look at the following: [VBox, HBox, Grid]

Nah, all three of those are for programmatically laying out components next to each other, e.g. a Cancel button next to an Ok button. What I had in mind is Paned, which gives the user a draggable divider for resizing.

Minda1975 commented 3 years ago

Hello. Also, terminator and kitty have this features.

cdepillabout commented 3 years ago

@gelisam Oh nice, Paned looks like it might work well.

@Minda1975 thanks for the pointers!

gelisam commented 3 years ago

There are probably also a few places in the code that make an assumption that there is a one-to-one mapping between Notebook tabs and Terminals

My attempts so far have been caught by assertInvariantTMState. It's refreshing to have such a formally-specified list of assumptions!

gelisam commented 3 years ago

Any particular reason why assertInvariantTMState is returning a list of errors instead of using a Validation monad, or even just Writer or ExceptT?

cdepillabout commented 3 years ago

Any particular reason why assertInvariantTMState is returning a list of errors instead of using a Validation monad, or even just Writer or ExceptT?

No good reason.

Feel free to refactor that function (or, really, any of the Termonad code) if it makes the implementation easier for you!

gelisam commented 3 years ago

All right, I finally have something to show! On my always-split branch, I have a version of Termonad in which every tab is split into two panes. I currently have a bug where exiting the shell of either pane will cause the tab to close, but won't cause the other pane's shell to exit, leaving orphan processes. But that seems minor compared to the bigger issue which follows.

While always having two panes is actually what I want, that's probably not what most users want, nor is it the feature I had in mind when I opened this issue. A more typical split-pane feature would start each tab with a single shell, and allow the user to recursively subdivide the area into more and more (and smaller and smaller) shells, and to close each pane independently. I don't have those features yet, but I do have a good idea of how to implement them.

Before I do, however, I think it's worth having a discussion about the split-paned API. Termonad boasts about being "extremely customizable", so after implementing proper split panes, it would be nice if I was then able to customize Termonad so that each tab starts with two panes rather than one. But (1) I am not very familiar with the way in which Termonad makes itself customizable, and (2) I worry about accidentally making a customization API which is too focused on my own use case. Any guidance?

cdepillabout commented 3 years ago

That all sounds really good. Definitely feel free to open a PR with what you currently have (or a draft PR if that makes more sense).

Termonad boasts about being "extremely customizable", so after implementing proper split panes, it would be nice if I was then able to customize Termonad so that each tab starts with two panes rather than one. But (1) I am not very familiar with the way in which Termonad makes itself customizable, and (2) I worry about accidentally making a customization API which is too focused on my own use case.

The customization features of Termonad aren't currently super well thought-out. Here's the config module: https://hackage.haskell.org/package/termonad-4.2.0.0/docs/Termonad-Config.html

You can see most things are just dumb, toggleable options. However, there is also a "hooks" feature that lets end users hook into different points in the execution of Termonad and do arbitrary actions. This is mostly based on the same ideas from XMonad:

https://hackage.haskell.org/package/termonad-4.2.0.0/docs/Termonad-Config.html#t:ConfigHooks

You can see that there is currently only one hook. There really haven't been many users asking for additional hooks, so I haven't added any others yet. Although it is something I'd like to do at some point.

So I think you basically have the following two choices for how to go about customizing the split pane stuff:

  1. Add a "dumb" configuration value to https://hackage.haskell.org/package/termonad-4.2.0.0/docs/Termonad-Config.html#t:ConfigOptions. This might be something like how many split panes to start by default, or what layout to have the split panes in that start by default (like two vertical, two horizontal, etc).

  2. Use the existing createTermHook hook for adding new panes upon tab creation in your own code. Or possibly adding a new hook that makes more sense for your use-case.

    It would probably be nice to create some high-level functions that other users could easily use if they also wanted to manipulate panes from their own hooks.

It sounds like you're thinking something like (2) makes more sense. I agree that seems reasonable.

gelisam commented 3 years ago

Definitely feel free to open a PR with what you currently have (or a draft PR if that makes more sense).

Ah, I forgot about draft PRs! Good idea, better start to discuss the changes early. Here is a draft PR.

craigem commented 3 years ago

Loving following this feature request.

When it was opened it did not hold much interest for me as I was working with termonad + tmux for years but I've recently become rather frustrated with how tmux mis-represents colours for tmux to be my all-day driver.

I'll be testing this pretty hard soon.

Thanks for making this happen! :smiley:

cdepillabout commented 3 years ago

@craigem I haven't been able to take a look at this (hopefully I can get to it in the next week or so), but any help testing would be really great!

formula-spectre commented 3 years ago

i would like to be a tester too, but I don't quite know how to; do I simply clone his branch, install and test split panes?

gelisam commented 3 years ago

Nice, users! Any customizations related to split panes you would like to see supported? Any suggestion for which keybindings should be used to split horizontally, split vertically, and to move between panes?

formula-spectre commented 3 years ago

personally I think ctrl-h and ctrl-v are good keybinding to split panels horizontally and vertically; maybe a ctrl-shift modifier? so it does not conflict with anything else. about customization.. maybe a way to resize the two panes? both via mouse and keybinding, though they both sound tricky to put into actual code.

gelisam commented 3 years ago

personally I think ctrl-h and ctrl-v are good keybinding to split panels horizontally and vertically; maybe a ctrl-shift modifier? so it does not conflict with anything else.

ctrl-v conflicts with vim's "don't interpret the next character, insert it literally", and ctrl-shift-v conflicts with termonad's existing Paste keybinding. Any other suggestion?

about customization.. maybe a way to resize the two panes? both via mouse and keybinding

the Paned widget is resizable by mouse already. I don't know if gtk already has some keybindings for resizing it via keyboard; I wasn't planning to put in any, but I guess I could. any suggestion for which keybinding?

though they both sound tricky to put into actual code.

are you saying you want to be able to customize whether the panes can be resized, or that you want a hook which allows you to compute a custom size when the pane is created, or that you want to be able to resize an existing pane from inside an existing hook?

Speaking of resizing, I have a UX question. Suppose you have the following split-panes arrangement:

+----+----+
|    |    |
|    |    |
|    |    |
|    +----+
|    |XXXX|
|    |XXXX|
|    |XXXX|
+----+----+

Your focus is on the XXXX pane, and you split the pane horizontally (that is, you split it into a top and a bottom pane). Do you expect the result to be

A) split that pane's area in two:

+----+----+
|    |    |
|    |    |
|    |    |
|    +----+
|    |XXXX|
|    +----+
|    |    |
+----+----+

B) redistribute the column's area equally:

+----+----+
|    |    |
|    |    |
|    +----+
|    |XXXX|
|    |XXXX|
+    +----+
|    |    |
|    |    |
+----+----+

C) something else

MuhammedZakir commented 3 years ago

[...] Any suggestion for which keybindings should be used to split horizontally, split vertically, and to move between panes?

Control+Shift+\ => split pane vertically Control+Shift+- => split pane horizontally

Control+Shift+[ => move to pane left Control+Shift+] => move to pane right Control+Shift+; => move to pane up Control+Shift+' => move to pane down

To adjust pane's dimension, use the above four key combo, but use Windows/Command/Meta instead of Control.


Speaking of resizing, I have a UX question. Suppose you have the following split-panes arrangement:

--snip--

Your focus is on the XXXX pane, and you split the pane horizontally (that is, you split it into a top and a bottom pane). Do you expect the result to be

A. Split that pane's area in two. Reason: others could mess up my "manually" created (vertical) layout.

formula-spectre commented 3 years ago

ctrl-v conflicts with vim's "don't interpret the next character, insert it literally", and ctrl-shift-v conflicts with termonad's existing Paste keybinding. Any other suggestion?

right, sorry haha, did not think about those

are you saying you want to be able to customize whether the panes can be resized, or that you want a hook which allows you to compute a custom size when the pane is created, or that you want to be able to resize an existing pane from inside an existing hook?

no no, I just meant resizing them via either mouse dragging or keybinding, sorry if I explained myself wrong

about the last question, I expect B, redestribute the column area equally.

sorry for the misunderstanding.

MuhammedZakir commented 3 years ago

Any customizations related to split panes you would like to see supported? [...]

How configurable should this be?

  1. Adjust pane height and width.
  2. Swap panes.
    • Simple swap between nearby panes is a must. But, is it also possible to swap between arbitrary panes?
  3. Color and thickness of border between panes.
    • I remember seeing issues about "thin" border between panes in another terminal repo.
  4. Hooks when a pane is created and when deleted.
    • should be called before the creation/deletion?
  5. What is passed to hooks? Pane or Tab?

Btw, I don't expect you to do everything yourself. I am hoping that you'll keep these in mind when implementing split panes to make it easier to add these in the future. :-)

craigem commented 3 years ago

Any suggestion for which keybindings should be used to split horizontally, split vertically, and to move between panes?

I've seen @MuhammedZakir suggestions and they appear sound. My off-the-cuff suggestion is a vague "let's look at what's commonly used elsewhere in TMUX, Byobu and other tools".

So I'll do some leg work on that and come back with a more meaning response than this.

craigem commented 3 years ago

I'll keep updating this comment with other examples but for starters, here's Tmux default key bindings for panes:

Action Tmux Terminator
toggle previous pane ctrl+b ; shift+ctrl+tab
split pane vertically ctrl+b " shift+ctrl+e
split pane horizontally ctrl+b % shift+ctrl+o
move current pane left ctrl+b { shift+ctrl+pageUp
move current pane right ctrl+b } shift+ctrl+pageDown
move to pane left ctrl+b ⇦ alt+⇦
move to pane right ctrl+b ⇨ alt+⇨
move to pane up ctrl+b ⇧ alt+⇧
move to pane down ctrl+b ⇩ alt+⇩
toggle between layouts ctrl+b space alt+l
switch to next pane ctrl+b o ctrl+tab
show pane numbers ctrl+b q n/a ?
select pane by number ctrl+b q 0..9 no default
toggle pane zoom ctrl+b z shift+ctrl+x
convert pane to window ctrl+b ! n/a
resize pane height up ctrl+b ctrl+⇧ shift+ctrl+⇧
resize pane height down ctrl+b ctrl+⇩ shift+ctrl+⇩
resize pane height right ctrl+b ctrl+⇨ shift+ctrl+⇨
resize pane height left ctrl+b ctrl+⇦ shift+ctrl+⇦
close current pane ctrl+b x shift+ctrl+w

Tmux has a concept of windows and panes whereas apps like Terminator only appear to have panes. The windows concept maps to tabs in Termonad nicely.

Looking at those keybindings, I'm glad I don't use terminator :rofl: I do use Tmux and find the modal vim-like usage comfortable and sane.

MuhammedZakir commented 3 years ago

Modal keybinding is more comfortable and easier to remember. If possible to do that, +1 for that.

I think we shouldn't touch arrow keys as it will interfere with navigation in command-line apps. For example, Alt+<arrow key> is heavily used for word navigation. This wouldn't be a problem if the keybinding is modal though. That's also another reason I am in favor of it. We can have sane shortucts like @craigem said.

craigem commented 3 years ago

If modal is chosen as the way forward, what should be the key?

Perhaps:

There are no doubt better ones that do not clash with common usage but that's a start.

MuhammedZakir commented 3 years ago

Perhaps:

ctrl+m for monads ctrl+o for open?

m can also stand for modal.

Two more:

Digit commented 3 years ago

ctrl+t's very tabby muscle memory already. alt+t ?