terminal/doc/specs/#532 - Panes and Split Wind...

9.8 KiB

author created on last updated issue id
Mike Griese @zadjii-msft 2019-05-16 2019-07-07 523

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 management 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 separated 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 horizontal 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 aforementioned 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 necessarily be turned off by the feature's discoverability.