diff --git a/doc/img/treetabs/tree_tabs_new_tab_types.png b/doc/img/treetabs/tree_tabs_new_tab_types.png new file mode 100644 index 00000000000..fdca0e01d4c Binary files /dev/null and b/doc/img/treetabs/tree_tabs_new_tab_types.png differ diff --git a/doc/img/treetabs/tree_tabs_overview_detail.png b/doc/img/treetabs/tree_tabs_overview_detail.png new file mode 100644 index 00000000000..fc5610ecf8c Binary files /dev/null and b/doc/img/treetabs/tree_tabs_overview_detail.png differ diff --git a/doc/treetabs.md b/doc/treetabs.md new file mode 100644 index 00000000000..7b14962015f --- /dev/null +++ b/doc/treetabs.md @@ -0,0 +1,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 create +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 into, has picked up some new behaviour to + decide where in relation to the current tab a new one should go. It has a + new `--sibling` argument and the `--related` argument, as well as `--tab` and + `-background`, has some additional meaning. +* `: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 who's behaviour will be modified 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`: `set-cmd-text -s :tree-tab-create-group -r` - r = related +* `zG`: `set-cmd-text -s :tree-tab-create-group` +* `za`: `tree-tab-toggle-hide` - same binding as vim folds +* `zp`: `tab-focus parent` +* `zo`: `set-cmd-text --space :open -tr` - r = related +* `zO`: `set-cmd-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 tree the tree structures 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 behaviour 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? diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 09405c3e75a..81c63032f36 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -109,11 +109,11 @@ def _copy_images(self) -> None: """Copy image files to qutebrowser/html/doc.""" print("Copying files...") dst_path = DOC_DIR / 'img' - dst_path.mkdir(exist_ok=True) - for filename in ['cheatsheet-big.png', 'cheatsheet-small.png']: - src = REPO_ROOT / 'doc' / 'img' / filename - dst = dst_path / filename - shutil.copy(src, dst) + try: + shutil.rmtree(dst_path) + except FileNotFoundError: + pass + shutil.copytree(REPO_ROOT / 'doc' / 'img', dst_path) def _build_website_file(self, root: pathlib.Path, filename: str) -> None: """Build a single website file."""