Skip to content
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

split panes #205

Open
gelisam opened this issue Oct 14, 2021 · 27 comments
Open

split panes #205

gelisam opened this issue Oct 14, 2021 · 27 comments

Comments

@gelisam
Copy link
Collaborator

gelisam commented Oct 14, 2021

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
Copy link
Owner

cdepillabout commented Oct 14, 2021

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:

-- Create a new terminal and launch a shell in it
vteTerm <- createAndInitVteTerm tmStateFontDesc (options tmStateConfig)
maybeCurrDir <- getCWDFromFocusedTab currNote
termShellPid <- launchShell vteTerm maybeCurrDir
tmTerm <- newTMTerm vteTerm termShellPid
-- Create the container add the VTE term in it
scrolledWin <- createScrolledWin mvarTMState
containerAdd scrolledWin vteTerm
-- Create the GTK widget for the Notebook tab
(tabLabelBox, tabLabel, tabCloseButton) <- createNotebookTabLabel
-- Create notebook state
let notebookTab = createTMNotebookTab tabLabel scrolledWin tmTerm
-- Add the new notebooktab to the notebook.
addPage mvarTMState notebookTab tabLabelBox

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:

  • newTabAction <- simpleActionNew "newtab" Nothing
    void $ onSimpleActionActivate newTabAction $ \_ -> void $ createTerm handleKeyPress mvarTMState
    actionMapAddAction app newTabAction
    applicationSetAccelsForAction app "app.newtab" ["<Shift><Ctrl>T"]
  • <item>
    <attribute name="label" translatable="yes">New _Tab</attribute>
    <attribute name="action">app.newtab</attribute>
    </item>

@cdepillabout
Copy link
Owner

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

@gelisam
Copy link
Collaborator Author

gelisam commented Oct 14, 2021

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
Copy link
Collaborator Author

gelisam commented Oct 15, 2021

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
Copy link
Contributor

Hello. Also, terminator and kitty have this features.

@cdepillabout
Copy link
Owner

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

@Minda1975 thanks for the pointers!

@gelisam
Copy link
Collaborator Author

gelisam commented Oct 20, 2021

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
Copy link
Collaborator Author

gelisam commented Oct 20, 2021

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

@cdepillabout
Copy link
Owner

cdepillabout commented Oct 20, 2021

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
Copy link
Collaborator Author

gelisam commented Oct 28, 2021

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
Copy link
Owner

cdepillabout commented Oct 28, 2021

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
Copy link
Collaborator Author

gelisam commented Oct 29, 2021

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
Copy link
Contributor

craigem commented Nov 9, 2021

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! 😃

@cdepillabout
Copy link
Owner

@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
Copy link

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
Copy link
Collaborator Author

gelisam commented Nov 9, 2021

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
Copy link

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
Copy link
Collaborator Author

gelisam commented Nov 9, 2021

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
Copy link

MuhammedZakir commented Nov 9, 2021

[...] 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
Copy link

formula-spectre commented Nov 9, 2021

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
Copy link

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
Copy link
Contributor

craigem commented Nov 9, 2021

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
Copy link
Contributor

craigem commented Nov 10, 2021

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 🤣 I do use Tmux and find the modal vim-like usage comfortable and sane.

@MuhammedZakir
Copy link

MuhammedZakir commented Nov 10, 2021

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
Copy link
Contributor

craigem commented Nov 10, 2021

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

  • ctrl+a is used by GNU screen
  • ctrl+b is used by TMUX
  • ctrl+c is right out 🤣

Perhaps:

  • ctrl+m for monads 😉
  • ctrl+o for open?

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

@MuhammedZakir
Copy link

Perhaps:

ctrl+m for monads
ctrl+o for open?

m can also stand for modal.

Two more:

  • ctrl+t for termonad
  • ctrl+s for shortcuts

@Digit
Copy link

Digit commented Nov 12, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants