Skip to content

Commit

Permalink
Add initial treetab docs.
Browse files Browse the repository at this point in the history
TODO:
* convert to asciidoc or make scripts/asciidoc2html.py support markdown
  input too.
  * for now I'm using `pandoc -f markdown -t html -o qutebrowser/html/doc/treetabs.html doc/treetabs.md`
  * should be able to asciidoc with pandoc too
* the last section and the rest of the second to last
* review and refactor
* post on the PR and figure out how we can contribute to it collectively
  (put on main branch so people can open PRs? create new integration
  repo where people can open issues, discussions and PRs?)
* maybe add more examples showing the effect of various operations
* (when including in a release) consider removing the implementation
  section, or refactoring it to be more high level broad strokes and
  less low level stuff that will inevitably drift from the actual
  implementation

These docs are not supposed to be in their final state. My plan for them
so far is to provide a guide to the changes for PR contributors. I think
it's currently difficult to hold the scope of the changes in your head
the description on the PR is currently very limited.

I would like to encourage discussion of usability and feature set as
well as highlighting some implementation details to point out upsides,
downsides, implications and alternatives.

I've modified asciidoc2html to copy all images over. It was only doing
specific ones before. But there are more images in there, why are they
in doc/img if they aren't images used in the documentation? Anyway,
storage is cheap, none of the images are sensitive and not having to
hardcode individual files names is good.
  • Loading branch information
toofar committed Jan 6, 2024
1 parent 1f84108 commit 1d6bea7
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 5 deletions.
Binary file added doc/img/treetabs/tree_tabs_new_tab_types.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/img/treetabs/tree_tabs_overview_detail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
212 changes: 212 additions & 0 deletions doc/treetabs.md
Original file line number Diff line number Diff line change
@@ -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

Check failure on line 41 in doc/treetabs.md

View workflow job for this annotation

GitHub Actions / linters (misc)

Found "behaviour" - Common misspelling or non-US spelling
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

Check failure on line 93 in doc/treetabs.md

View workflow job for this annotation

GitHub Actions / linters (misc)

Found "behaviour" - Common misspelling or non-US spelling
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

Check failure on line 158 in doc/treetabs.md

View workflow job for this annotation

GitHub Actions / linters (misc)

Found "behaviour" - Common misspelling or non-US spelling
* 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?
10 changes: 5 additions & 5 deletions scripts/asciidoc2html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down

0 comments on commit 1d6bea7

Please sign in to comment.