summaryrefslogtreecommitdiff
path: root/doc/treetabs.md
blob: f32ee5449440189efa0b2bbfaab824d6640a3ded (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# Tree Style Tabs

## Intro

Tree style tabs allow you to group and manage related tabs together. Related
tabs will be shown in a hierarchical fashion in the tab bar when it is on the
left or right side of the browser window. It can be enabled by setting
`tabs.tree_tabs` to `true`. That setting only applies to new windows created
after it is enabled (including via saving and loading a session or
`:restart`).

![](img/treetabs/tree_tabs_overview_detail.png)

When a tab is being opened it will be classified as one of *unrelated*
(default), *sibling* or *related* to the current tab.

![](img/treetabs/tree_tabs_new_tab_types.png)

* *unrelated* tabs are created at the top level of the tree for the current
  browser window. They can be created by opening a new tab using `:open -t`.
* *sibling* tabs are created at the same level as the current tab. They can be
  created by running `:open -t -S`.
* *related* tabs are created as children of the current tab. They can be
  created by following a link in a new tab (middle click, `F` hinting mode) or
  by running `:open -t -r`.

## Enabling Tree Tabs

TODO: more words here

* `tabs.tree_tabs`
* check default settings: title format, padding, elide
* steps to take when downgrading if you don't want to lose settings

## Manipulating the Tree

todo: add animated illustrations?

You can change how tabs relate to each other after they are created too.

* `:open`, as described in the intro, has picked up some new behavior to
  decide where in relation to the current tab a new one should go. It has a
  new `--sibling` argument and the existing arguments `--related`, `--tab` and
  `--background` have picked up some additional meaning to help with that.
* `:tab-move` will move a tab and its children within the tree
    * With a `+` or `-` argument tabs will only move within their siblings
      (wrapping at the top or bottom)
    * With a count or integer argument tabs will move to the absolute position
      specified, which may include changing level in the hierarchy.
* Tabs can be moved up and down a hierarchy with the commands
  `:tree-tab-promote` and `:tree-tab-demote`
* `:tab-give --recursive` will move a tab and its children to another window.
  They will be placed at the top level.
* Some methods of moving tabs do *not* yet understand tab groups, these are:
    * `:tab-take`
    * moving tabs with a mouse or other pointer

Other pre-existing commands that understand tab groups are:

* `:tab-close --recursive` will close a tab and all its children. If
  `:tab-close` is used without `--recursive` the first of a tabs children will
  be promoted in its place.
* `:tab-focus parent` will switch focus to a tab's parent, so that you don't
  have to cycle through a tab's siblings to get there.
* `:tab-next --sibling` and `:tab-prev --sibling` will switch the focus to a
  tab's sibling, skipping any child tabs.

## Working with Tab Groups

Beyond the commands above for manipulating the tree, there are a few new
commands introduced to take advantage of the tab grouping feature.

* `:tree-tab-create-group {name}` will create a new placeholder tab with a
  title of `{name}`. This is a light weight way of creating a "named group" by
  putting a tab with a meaningful title at the top level of it. It can
  create tabs at the top level of the window or under the current tab with the
  `--related` argument. The placeholder tab contains an ascii art picture of a
  tree. The title of the tab comes from the URL path.
* `:tree-tab-toggle-hide` will collapse, or reveal, a tab group, which will
  hide any children tabs from the hierarchy shown in the tab bar as well as
  making children unelectable via `:tab-focus`, `tab-select` and `:tab-take`.
  The tabs will still be running in the background.
* `:tree-tab-cycle-hide` will hide successive levels of a tab's hierarchy of
  children. For example, the first time you run it will hide the outermost
  generation of leaf nodes, the next time will hide the next level up and so
  on.
* `:tree-tab-suspend-children` will suspend all of the children of a tab via
  the lazy load mechanism (`qute://back/`). Tabs will be un-suspended when
  they are next focused. This apply for any children which are hidden too.

## Settings

There are some existing settings that will have modified behavior when tree
tabs are enabled:

* `tabs.new_position.related`: this is essentially replaced by
  `tabs.new_position.new_child`
* `tabs.new_position.unrelated`: this is essentially replaced by
  `tabs.new_position.new_toplevel`
* the settings `tabs.title.format`, `tabs.title.format_pinned` and
  `window.title_format` have gained two new template variables: `{tree}` and
  `{collapsed}`. These are for displaying the tree structure in the tab bar and
  the default value for `tabs.title.format` now has `{tree}{collapsed}` at the
  start of it.

There are a few new settings introduced to control where tabs are places in
the tree structure as a result of various operations. All of these settings
accept the options `first`, `last`, `next` or `prev`; apart from `new_child`
and `demote` which only accept `first` or `last`.

* `tabs.new_position.promote`
* `tabs.new_position.demote`
* `tabs.new_position.new_toplevel`
* `tabs.new_position.new_sibling`
* `tabs.new_position.new_child`

## Bindings

There are various new default bindings introduced to make accessing the new
and changed commands easy. They all start with the letter `z`:

TODO: more words here? Are any of these bindings analogous to existing
ones? Any theme to them?

* `zH`: `tree-tab-promote`
* `zL`: `tree-tab-demote`
* `zK`: `tab-prev -s` - cycle tab focus upwards among siblings
* `zJ`: `tab-next -s` - cycle tab focus downwards among siblings
* `zd`: `tab-close -r` - r = recursive
* `zg`: `cmd-set-text -s :tree-tab-create-group -r` - r = related
* `zG`: `cmd-set-text -s :tree-tab-create-group`
* `za`: `tree-tab-toggle-hide` - same binding as vim folds
* `zp`: `tab-focus parent`
* `zo`: `cmd-set-text --space :open -tr` - r = related
* `zO`: `cmd-set-text --space :open -tS` - S = sibling

## Implementation

The core tree data structure is in `qutebrowser/misc/notree.py`, inspired by
the `anytree` python library. It defines a `Node` type. A Node can have a
parent, a list of child nodes, and `value` attribute - which in qutebrowser's
case is always a browser tab. A tree of nodes is always modified by changing
either the parent or children of a node via property setters. Beyond those two
setters nodes have `promote()` and `demote()` helper functions used by the
corresponding commands.

Beyond those four methods to manipulate the tree structure nodes have
methods for:

* traversing the tree:
  * `traverse()` return all descendant nodes (including self)
  * `path()` return all nodes from self up to the tree root, inclusive
  * `depth()` return depth in tree
* collapsing a node
  * this just sets an attribute on a node, the traversal function respects it
    but beyond that it's up to callers to know that an un-collapsed node may
    be hidden if a parent node is collapsed, there are a few pieces of
    calling code which do implement different behavior for collapsed nodes
* rendering unicode tree segments to be used in tab titles
  * our tab bar itself doesn't understand the tree structure for now, it's
    just being represented by drawing unicode line and angle characters to
    the left of the tab titles which happen to line up
  * this does generally put some restrictions on some tab bar related
    settings. `tabs.title.format` needs to have `{tree}{collapsed}` in it,
    `tabs.padding` needs to have 0 for the top and bottom padding,
    `tabs.title.elide` can't be on the same side as the tree related format strings.

Beyond the core data structure most of the changes are in places where tabs
need to relate to each other. There are two new subclasses of existing core
classes:

*TreeTabbedBrowser* inherits the main TabbedBrowser and has overriden methods
to make sure tabs are correctly positioned when opening a tab, closing a tab
and undoing a tab close. After tabs are opened they are placed into the
correct position in the tree based on the new `tabs.new_position.*` settings
and then into order in the tab widget corresponding to the tree traversal
order. When tabs are closed the new `--recursive` flag is handled, children
are re-parented in the tree and extra details are added to undo entries. When
a tab close is undone its position in the tree is restored, including demoting
any child that was promoted when the tab was closed. TreeTabbedBrowsers will
be created by MainWindow when the new `tabs.tree_tabs` setting is set.

*TreeTabWidget* handles making sure the new `{tree}` and `{collapsed}` are
filled in for the tab title template string, with a lot of help from the data
structure. It also handles hiding or showing tabs for collapsed
groups/branches. Hidden tabs are children of tabs with the `collapsed`
property set, they remain in the tree structure (which is held by the tabbed
browser) but they are removed entirely from the tab widget. It also handles
making sure tabs are moved to indexes corresponding to their traversal order
in the tree if any changes to the tree structure happen via the
`tree_tab_update()` method that is called from several places.

A fair amount of tree tab specific code lives in *commands.py*. The six new
commands have been added, as well as a customization so that these commands
don't show up in the command completion if the tree tabs feature isn't
enabled. The commands for manipulating the tree structure do very little but
call out to other pieces of code, either the browser or the tree structure.
Of note are the two commands `tree_tab_create_group()` and
`tree_tab_suspend_children()` which use the scheme handlers `qute://treegroup`
(new) and `qute://back` (existing).

Beyond those six new commands quite a few existing commands to do with
manipulating tabs have seen some tree tab specific code paths added, some of
them quite complex and with little shared with the existing code paths. Common
themes beyond handling new arguments are dealing with recursive operations and
collapsed nodes.

something something sessions.py

Other stuff, like tree group page

## Outstanding issues? Questions?