Navigation Menu

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

Add support for multiple panes in the same window #825

Merged
merged 41 commits into from Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
606dd57
Start working on adding support for panes
zadjii-msft May 8, 2019
91dc273
Add basic pane splitting
zadjii-msft May 9, 2019
a600b20
This works for nesting panes
zadjii-msft May 9, 2019
faaba47
Switching tabs keeps focus on the last active pane now
zadjii-msft May 10, 2019
9b792d3
Reload settings for the panes.
zadjii-msft May 14, 2019
7ca3e8d
Get titles working again
zadjii-msft May 14, 2019
db7523a
Update the icon too
zadjii-msft May 14, 2019
7258a71
Pull these two guys into helpers
zadjii-msft May 14, 2019
29464ea
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 14, 2019
377aa4f
Hook up the terminal's closing to the pane's to the tab's
zadjii-msft May 14, 2019
23930b5
This is me trying to mess with the tab closing, but I think it's prob…
zadjii-msft May 14, 2019
a4fbc53
Revert "This is me trying to mess with the tab closing, but I think i…
zadjii-msft May 14, 2019
b353e89
TONS of polish. Doc comments, etc.
zadjii-msft May 14, 2019
dc3f522
Correctly close a pane when one of it's children has children
zadjii-msft May 15, 2019
d45a9aa
Correctly move focus to a child with children when the focused pane i…
zadjii-msft May 15, 2019
0a96ed4
doc comments for days
zadjii-msft May 15, 2019
a754474
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 17, 2019
ff21bd2
This is most all of the PR feedback
zadjii-msft May 17, 2019
ba805e8
Update doc/cascadia/Panes.md
zadjii-msft May 17, 2019
3d1c4ca
Mostly just PR nits
zadjii-msft May 20, 2019
013cefa
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 20, 2019
7c5c222
Fix some bugs with closing a leaf _after_ it was a parent.
zadjii-msft May 21, 2019
cd75b1d
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 21, 2019
d5060b3
Merge branch 'master' into dev/migrie/f/panes
zadjii-msft May 23, 2019
6d847dc
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 23, 2019
0f848e7
Remove the default keybinding.
zadjii-msft May 24, 2019
bbed9ed
switch to in-class initializers
zadjii-msft May 24, 2019
704f6cc
Update doc/cascadia/Panes.md
zadjii-msft May 30, 2019
d016d5c
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft May 30, 2019
40c5600
Apply suggestions from code review
zadjii-msft May 31, 2019
890fb3a
Add a NOTICES file.
DHowett May 31, 2019
0ea8b85
move the NOTICE to the right branch...
DHowett May 31, 2019
80036e2
Much of the easier PR feedback
zadjii-msft Jun 3, 2019
9165d02
Lock up the panes when they're getting opened/closed
zadjii-msft Jun 3, 2019
7f50a0f
A little bit of cleanup on the comments here
zadjii-msft Jun 3, 2019
b5d954e
Refactor the code for actually doing a split
zadjii-msft Jun 4, 2019
c6311f7
Clean up some typos
zadjii-msft Jun 7, 2019
a76db18
Merge remote-tracking branch 'origin/master' into dev/migrie/f/panes
zadjii-msft Jun 7, 2019
c265317
Fixed build error
carlos-zamora Jun 7, 2019
14f3ffa
Apply suggestions from code review
zadjii-msft Jun 7, 2019
f945c18
Apply suggestions from code review
zadjii-msft Jun 7, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
232 changes: 232 additions & 0 deletions doc/cascadia/Panes.md
@@ -0,0 +1,232 @@
---
author: "Mike Griese @zadjii-msft"
created on: 2019-May-16
---

# Panes in the Windows Terminal

## Abstract

Panes are an abstraction by which the terminal can display multiple terminal
instances simultaneously in a single terminal window. While tabs allow for a
single terminal window to have many terminal sessions running simultaneously
within a single window, only one tab can be visible at a time. Panes, on the
other hand, allow a user to have many different terminal sessions visible to the
user within the context of a single window at the same time. This can enable
greater productivity from the user, as they can see the output of one terminal
window while working in another.

This spec will help outline the design of the implementation of panes in the
Windows Terminal.

## Inspirations

Panes within the context of a single terminal window are not a new idea. The
design of the panes for the Windows Terminal was heavily inspired by the
application `tmux`, which is a commandline application which acts as a "terminal
multiplexer", allowing for the easy managment of many terminal sessions from a
single application.

Other applications that include pane-like functionality include (but are not
limited to):

* screen
* terminator
* emacs & vim
* Iterm2

## Design

The architecture of the Windows Terminal can be broken into two main pieces:
Tabs and Panes. The Windows Terminal supports _top-level_ tabs, with nested
panes inside the tabs. This means that there's a single strip of tabs along the
application, and each tab has a set of panes that are visible within the context
of that tab.

Panes are implemented as a binary tree of panes. A Pane can either be a leaf
pane, with it's own terminal control that it displays, or it could be a parent
pane, where it has two children, each with their own terminal control.

When a pane is a parent, its two children are either split vertically or
horizontally. Parent nodes don't have a terminal of their own, they merely
display the terminals of their children.

* If a Pane is split vertically, the two panes are seperated by a vertical
split, as to appear side-by-side. Think `[|]`
* If a Pane is split horizontally, the two panes are split by a horizontal
separator, and appear above/below one another. Think `[-]`.

As additional panes are created, panes will continue to subdivide the space of
their parent. It's up to the parent pane to control the sizing and display of
it's children.

### Example

We'll start by taking the terminal and creating a single vertical split. There
are now two panes in the terminal, side by side. The original terminal is `A`,
and the newly created one is `B`. The terminal now looks like this:

```
+---------------+
| | | 1: parent [|]
| | | ├── 2: A
| | | └── 3: B
| A | B |
| | |
| | |
| | |
+---------------+
```

Here, there are actually 3 nodes: 1 is the parent of both 2 and 3. 2 is the node
containing the `A` terminal, and 3 is the node with the `B` terminal.


We could now split `B` in two horizontally, creating a third terminal pane `C`.

```
+---------------+
| | | 1: parent [|]
| | B | ├── 2: A
| | | └── 3: parent [-]
| A +-------+ ├── 4: B
| | | └── 5: C
| | C |
| | |
+---------------+
```

Node 3 is now a parent node, and the terminal `B` has moved into a new node as a
sibling of the new terminal `C`.

We could also split `A` in horizontally, creating a fourth terminal pane `D`.

```
+---------------+
| | | 1: parent [|]
| A | B | ├── 2: parent [-]
| | | | ├── 4: A
+-------+-------+ | └── 5: D
| | | └── 3: parent [-]
| D | C | ├── 4: B
| | | └── 5: C
+---------------+
```

While it may appear that there's a single horizonal separator and a single
vertical separator here, that's not actually the case. Due to the tree-like
structure of the pane splitting, the horizontal splits exist only between the
two panes they're splitting. So, the user could move each of the horizontal
splits independently, without affecting the other set of panes. As an example:

```
+---------------+
| | |
| A | |
+-------+ B |
| | |
| D | |
| +-------+
| | C |
+---------------+
```

### Creating a pane

In the basic use case, the user will decide to split the currently focused pane.
The currently focused pane is always a leaf, because as parent's can't be
focused (they don't have their own terminal). When a user decides to add a new
pane, the child will:

1. Convert into a parent
2. Move its terminal into its first child
3. Split its UI in half, and display each child in one half.

It's up to the app hosting the panes to tell the pane what kind of terminal in
wants created in the new pane. By default, the new pane will be created with the
default settings profile.

### While panes are open

When a tab has multiple panes open, only one is the "active" pane. This is the
pane that was last focused in the tab. If the tab is the currently open tab,
then this is the pane with the currently focused terminal control. When the user
brings the tab into focus, the last focused pane is the pane that should become
focused again.

The tab's state will be updated to reflect the state of it's focused pane. The
title text and icon of the tab will reflect that of the focused pane. Should the
focus switch from one pane to another, the tab's text and icon should update to
reflect the newly focused control. Any additional state that the tab would
display for a single pane should also be reflected in the tab for a tab with
multiple panes.

While panes are open, the user should be able to move any split between panes.
In moving the split, the sizes of the terminal controls should be resized to
match.

### Closing a pane

A pane can either be closed by the user manually, or when the terminal it's
attached to raises its ConnectionClosed event. When this happens, we should
remove this pane from the tree. The parent of the closing pane will have to
remove the pane as one of it's children. If the sibling of the closing pane is a
leaf, then the parent should just take all of the state from the remaining pane.
This will cause the remaining pane's content to expand to take the entire
boundaries of the parent's pane. If the remaining child was a parent itself,
then the parent will take both the children of the remaining pane, and make them
the parent's children, as if the parent node was taken from the tree and
replaced by the remaining child.

## Future considerations

The Pane implementation isn't complete in it's current form. There are many
additional things that could be done to improve the user experience. This is by
no means a comprehensive list.

* [ ] Panes should be resizable with the mouse. The user should be able to drag
the separator for a pair of panes, and have the content between them resize as
the separator moves.
* [ ] There's no keyboard shortcut for "ClosePane"
* [ ] The user should be able to configure what profile is used for splitting a
pane. Currently, the default profile is used, but it's possible a user might
want to create a new pane with the parent pane's profile.
* [ ] There should be some sort of UI to indicate that a particular pane is
focused, more than just the blinking cursor. `tmux` accomplishes this by
colorizing the separators adjacent to the active pane. Another idea is
displaying a small outline around the focused pane (like when tabbing through
controls on a webpage).
* [ ] The user should be able to navigate the focus of panes with the keyboard,
instead of requiring the mouse.
* [ ] The user should be able to zoom a pane, to make the pane take the entire
size of the terminal window temporarily.
* [ ] A pane doesn't necessarily need to host a terminal. It could potentially
host another UIElement. One could imagine enabling a user to quickly open up a
Browser pane to search for a particular string without needing to leave the
terminal.

## Footnotes

### Why not top-level panes, and nested tabs?

If each pane were to have it's own set of tabs, then each pane would need to
reserve screen real estate for a row of tabs. As a user continued to split the
window, more and more of the screen would be dedicated to just displaying a row
of tabs, which isn't really the important part of the application, the terminal
is.

Additionally, if there were top-level panes, once the root was split, it would
not be possible to move a single pane to be the full size of the window. The
user would need to somehow close the other panes, to be able to make the split
the size of the dull window.

One con of this design is that if a control is hosted in a pane, the current
design makes it hard to move out of a pane into it's own tab, or into another
pane. This could be solved a number of ways. There could be keyboard shortcuts
for swapping the positions of tabs, or a shortcut for both "zooming" a tab
(temporarily making it the full size) or even popping a pane out to it's own
tab. Additionally, a right-click menu option could be added to do the
aformentioned actions. Discoverability of these two actions is not as high as
just dragging a tab from one pane to another; however, it's believed that panes
are more of a power-user scenario, and power users will not neccessarily be
turned off by the feature's discoverability.