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

Implement Tree Tabs #4602

Closed
wants to merge 268 commits into from
Closed

Implement Tree Tabs #4602

wants to merge 268 commits into from

Conversation

pinusc
Copy link
Collaborator

@pinusc pinusc commented Feb 22, 2019

As discussed in #927.

This pull request implements Tree Tabs in qutebrowser in a similar fashion to Firefox's Tree-Style-Tabs plugin. It is stable enough for everyday use, but it needs testing and review.

Implemented features:

  • Basic tree-tabs functionality
  • notree.py module to replace anytree dependency
  • Setting to toggle tree-tabs functionality: tabs.tree_tabs
  • Folding tab groups
  • User-created groups (qute://tabgroup/title)
  • Commands to manipulate tree-tabs:
    • :tab-close --recursive
    • :tree-tab-promote and :tree-tab-demote
    • :tree-tab-toggle-hide and :tree-tab-cycle-hide
    • :tab-move moves entire subtrees correctly
  • :tree-focus parent and :tab-prev/tab-next --sibling
  • :session-save preserves tree structures
  • Unrelated tabs are created under root node, and they follow set tabs.new_position.unrelated
  • undo restores correct parent & children of the tab, and restores the entrire tree closed by :tab-close -r
  • Settings tabs.new_position.tree.{demote,new_child,new_sibling,new_toplevel,promote}
  • Unit tests

Known issues:

  • Tab indicators disappear at :tree-tab-toggle-hide
  • tree_prefix is empty! errors at startup
  • Needs more unit tests?
  • Mypy errors
  • Lazier load (Lazier load #4037) should probably be merged before this. After that, :tab-unload --recursive may be implemented. With Lazier load #4037 suspended tabs' titles are lost at collapse.
  • When opening a new child tab at a collapsed tree, the new tab is invisible. Maybe the tree should be expanded?
  • Restoring a session after a crash does not successfully recreate the named tree group pages
  • Tab dragging with the mouse does not work, and it probably won't be fixed in this pull request.
  • Hidden tabs are not searchable/selectable with :buffer, and it probably won't be implemented in this pull request.

The underlying structures always stay enabled as the overhead is very small;
this option only toggles show of the tree
This is almost a hack. Since tabs do not have unique identifiers that can be
saved by SessionManager, their indexes are used as identifier instead. This
relies on tabs being saved (and loaded) in the right order. Then a list of
indexes is saved to memorize the tree.
Was there for debug but useless now
:open qute://treegroup/Name%20of%20group will open a tab that can be used as
user-defined group. Furthermore, command :tree-tab-create-group wraps this open
action (mainly to properly quote spaces and special characters so they can be
part of the title)
Now there are no more idiotically complex checks. "collapsed" is a property of
Node objects.

This also allowed correct tree rendering for subtrees which contain collapsed
descendents
It makes more sense as value is not generally a string --- especially when it's
a tab
First step towards memoization
It's not used and it never will be; if the user wants that, they'll just
un-collapse their tabs
Man, I should really test even stupid changes before pushing
To reflect arguments to other tree-tab commands
When a collapsed tab group was being deleted the nodes were being removed
from the tree by a sneaky line in `_add_undo_entry()`, which made it difficult
to reason about the code. The nodes are removed from the tree by setting
`node.parent = None`, the setting goes and calls `parent.disown()`, which is a
bit magical but that's a job for another day.

This commit moves the line where the node is removed from the tree to be up into
the same block as where the tab is deleted and adjusts the undo entry to
remove the child uids from it instead. The problem there is that
`UndoEntry.from_node()` iterates over a nodes children, previously
children were being removed from the tree first so they weren't showing
up here. We don't want children uids in the undo nodes because the
restore code will look for an existing child to re-attach when
re-creating a node. But tabs are restored parent first. And since we do
have a parent uid in the undo entry that is enough to restore the full
structure.
Little bit of tidy up. This was already being used in the conditional branch
for collapsed nodes, I have no idea why it wasn't being used for the
un-collapsed case.
Two issues here:

1. transplanted trees could get rendered upside down depending on the value of the
   tab.new_position.tree.new_child setting, I think. The parent and child
   attributes of the nodes looked correct but it was being rendered upside
   down in the tab bar, weird. Anyway, we are adding new tabs at the top level
   and them setting their parents based on the mapping. We shouldn't be
   opening them as related to the currently focused tab
2. moving a subtree was crashing because the parent of the top node wasn't in
   the mapping. Which is fine because that's not being moved over. I changed
   it to not explicitly change the parent of such nodes (so they remain under
   the tree root).

Two other tidy ups:

* the initialization of "uid_map" as it didn't seem to need the 1:1 in there.
* remove tabs explicitly in reverse order rather than repeatedly
  removing the current tab. This avoids re-work in the tree because it
  does a bunch of stuff to re-parent children if you delete the parent.
  Also I have debug logging in notree currently warning of children being
  "orphaned" that was going off

TODO items around tab moving:

* collapsed state is lost
* moved tab isn't focused in destination window (I think that's a thing that
  happens normally? Could be wrong)
* this method might be a bit simpler and consistent with other methods
  (like undo) if it did more stuff in one go using traverse order to
  keep things consistent, instead of the map. It seems to work fine as
  is but not re-using patterns makes it harder to see opportunities for
  refactoring

The structure I was testing with was:

1. one
  2. two
    3. three
      4. four
5. five

1. six

Then focusing "three" and `:tab-give -r 1`.
Hopefully it saves someone else from having to interpret it in the
future.

Also change the last line into an assert because is should be redundant.

This method seems more complex than it should be. Should, or is, some of
this functionality be available in the notree data structure?
I think when removing nodes it's less work to remove leaves first, so that
children don't have to be reparented. We also don't need to save everything to
a list. I don't know what the difference out of POST and POST_R, maybe if you
remove the last sibling first it's less work? Not sure.

Also take care of a fixme comment which is fixed (the comment it refers to is
still there, but add_undo_entry is no longer modifying the tree).
The root node never is associated with a tab.
We shouldn't really be closing tabs in multiple places[^1], but while we
are, lets not promulgate copypasta we don't need.

layout.unwrap() was previously called as a workaround for some upstream
behaviour in Qt < 5.12. That workaround was removed in c067a96

[^1]: I want to make it so nodes know how to close tabs, that we we only have to worry about
      either widget stuff or tree stuff, not both
Seems a step from the existing code path wasn't applied to the
new tree-specific code. I've pulled it out to be applied to both paths.
Previously if you ran gD (tab-give without a positional argument) it
would open a new window but you couldn't see it!
When recursive closing a tree group you will be prompted if you want to
close any tabs that are pinned. Except we passed the topmost tab to the
prompt method every time instead of the tab we were going to close.
This fixes that. It does mean that if there are multiple pinned tabs in
a tree it gives you an identical prompt for each one, which no
indication that they are separate prompts. It looks like nothing is
happening when you answer it. But that's a separate problem (probably
the solution is to either prompt once or to put URLs and/or tab IDs in
the prompt).
I was looking through some commit messages from last week and noticed I
was having trouble keeping track of what these traverse orders meant. So
I tried to adjust the descriptions to be more clear. Hopefully it
worked?

Probably could still use a visual aid though, or the enum value names
could even be changed to be more clear, like "parent-first",
"child-first", "child-first-reverse/-right".

The test I used to verify this description was:

    simple_tree = Node('parent')
    left = Node('left', simple_tree)
    Node('grandleft-left', left)
    Node('grandleft-right', left)
    Node('middle', simple_tree)
    right = Node('right', simple_tree)
    Node('grandright', right)
    # parent
    # ├─ left
    # │ ├─ grandleft-left
    # │ └─ grandleft-right
    # ├─ middle
    # └─ right
    #   └─ grandright

    @pytest.mark.parametrize('order, expected', [
      (TraverseOrder.PRE, "parent left grandleft-left grandleft-right middle right grandright",),
      (TraverseOrder.POST, "grandleft-left grandleft-right left middle grandright right parent",),
      (TraverseOrder.POST_R, "grandright right middle grandleft-right grandleft-left left parent",),
    ])
    def test_simple(order, expected):
        assert expected == " ".join(
            [n.value for n in simple_tree.traverse(order)]
        )
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.
@toofar
Copy link
Member

toofar commented Jan 6, 2024

I've pushed a few fixes and cleanups I made while working to understand this PR over the last couple of weeks. I started out getting rid of the "tree_prefix is empty!" error, that led me into a bit of a rabbit hole where I observed situations where the tree structure and the tab widget got out of sync. It was only for small amounts of time, but if it can happen then any bit of code that deals with those two structure might have to account for it, which could be problematic. I think there's the possibility of a more general fix if we move to using signals to keep things in sync instead of calling tree_tab_update(), but I haven't got that far yet. Instead I changed a couple of pieces of code to not trigger that situation so far.

I did leave an assert in there in case there are any more cases where they get out of sync, hopefully I've manually tested all the functionality enough and caught everything!

Apart from that I did a few cleanups and fixed tab-give and tab-give -r <win-id> but mostly tried to avoid distracting myself with too many changes for now. I made a start adding some documentation for this feature. I'm not sure what it's final state would be but for now I hope it acts as a guide to this PR. There's a lot going on here and very little written about it in commit messages or the PR description, so hopefully this helps anyone who wants to jump in and get involved. Feel free to treat it as collaboratively as the code.

@pinusc
Copy link
Collaborator Author

pinusc commented Jan 6, 2024

Thanks for the excellent work @toofar! I have been meaning to pick up this PR again and get it into mergeable shape for a very long time; starting in the second half of January I should be able to start doing some actual work, in terms of review, discussing plans, and actually refactoring and fixing what's broken.

I honestly have forgotten how most of the code works, but I do remember having broadly similar worries regarding duplication and overridden classes. I was young and inexperienced (maybe I still am), and this was probably too big a PR for me! So I really appreciate the notes, and I would love to keep discussing how to move forward with this feature. To be clear, I absolutely do not mind relinquishing what little "control" I had before, if you want to take charge; but I would like to still be involved, assuming I will be able to put in time and effort starting a couple weeks from now.

@toofar
Copy link
Member

toofar commented Jan 7, 2024

@pinusc it's good to see that the architect is still around and engaged! Sorry for springing this on you, I've been wanting to get a larger change merged for a while and I've been eyeing up this PR.

I hope we can keep momentum up and get this merged without any more breaks but I don't think there will be a huge amount of progress in the next couple of weeks, so I'm sure there will be lots for you to dig into when you have time.

Do you have any thoughts about how we could discuss things like outstanding tasks, their priorities and implementation ideas? We could do it on this PR but I think it's a bit linear for that and it would be hard to have multiple discussion going?

@toofar
Copy link
Member

toofar commented Jan 19, 2024

I had a suggestion of using a project board and labels to track outstanding tasks related to this feature, I've started one off here: https://github.com/orgs/qutebrowser/projects/3/views/1

For now I've put emphasis on fleshing out the larger "blocker" tasks because while there are other valuable contributions we could be doing now, lots of them could also be done after the bulk of the work is merged into the main branch. I've put some words on those blocker issues and a size and priority rating. The sizes didn't turn out to be very granular, maybe I should have used a larger range. Or maybe it's just because I've only gone through the larger ones so far. For me I'll try to continue with the documentation and fleshing out the project board over the next week.

@pinusc if it's okay with you I'll create a new tree-tabs-integration branch in this repo with your feature/tree-tabs branch on it. That'll mean people can open PRs on this repo against it. Then I'll close this PR and open a new draft one to track the overall feature. Sadly I don't think you can change the source repo of a PR.

@pinusc
Copy link
Collaborator Author

pinusc commented Jan 22, 2024

@toofar everything sounds great; feel free to close this PR and move development to an actual branch. The project board is also the perfect solution to the organization problem.
In the next couple days I will actually start working on this again, so I really appreciate the organizational work you've done

@pinusc
Copy link
Collaborator Author

pinusc commented Jan 24, 2024

@toofar Any chance you could make me a collaborator / give me write access on the tree-tab github project? I'd actually like to start working on re-standardizing the session format, as it's a small enough issue that will help me refamiliarize myself with my own code.

Also, once we move development to a qutebrowser branch, would I be able to push to it or would I need to make PRs towards that branch?

@pinusc
Copy link
Collaborator Author

pinusc commented Jan 24, 2024

If someone is using this PR daily: the last commit (a2f98aa) will break existing tree tab sessions, as it changes the format.

@toofar
Copy link
Member

toofar commented Jan 25, 2024

Any chance you could make me a collaborator / give me write access on the tree-tab github project?

Check your mailbox, you should have one invite from the 15th (invite to the org) and one from today (to the repo, because I couldn't find the org level thing again and thought I imagined it).

break existing tree tab sessions

;_;

Also, once we move development to a qutebrowser branch, would I be able to push to it or would I need to make PRs towards that branch?

You will need to raise PRs. And that's a good thing! Gives us a space to discuss individual changes in isolation.

@pinusc
Copy link
Collaborator Author

pinusc commented Jan 25, 2024

Check your mailbox, you should have one invite from the 15th (invite to the org) and one from today (to the repo, because I couldn't find the org level thing again and thought I imagined it).

I got the invite to the repo, but it didn't give me write access to the project board. Which makes some sense, as the project is at the org level. I hadn't seen the org invite from the 15th and it expired.

I think you can give me access to the project directly from the project settings -> manage access; that's all I should need, anyway.

break existing tree tab sessions

;_;

I hope that's okay! I agreed that it would be better if the format was compatible with 'vanilla', and I figured we'd have to break compatibiliity with my previous implementation at some point.

You will need to raise PRs. And that's a good thing! Gives us a space to discuss individual changes in isolation.

Yeah, that absolutely makes sense

@toofar toofar mentioned this pull request Jan 26, 2024
@toofar
Copy link
Member

toofar commented Jan 26, 2024

I'm closing this PR now because development is moving to this new one where the branch is hosted in this repo: #8082

In case it wasn't clear tree tabs isn't merged yet, nor is it forgotten. It's being actively worked on, we aim to reflect progress on this project board. It's our first time doing that through so please be patient.

I got the invite to the repo, but it didn't give me write access to the project board.

It lists you there now, so hopefully it worked. It didn't let me add people who weren't members or contributors of the org. Hence the invites. Should all be sorted now.

@toofar toofar closed this Jan 26, 2024
Pull request backlog automation moved this from WIP to Done Jan 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Tree style tabs