-
-
Notifications
You must be signed in to change notification settings - Fork 409
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
Global history tree #1007
Global history tree #1007
Conversation
This comment has been minimized.
This comment has been minimized.
Wow! Yet another impressive patchset by @aartaka! :) |
This change is to make history handling coherent with recent changes (#910) and to open a way for new features (#247, #854, #958).
Can you explain how this change would help with incognito mode?
- Should `web-mode` actually have a separate history? Is it okay that it's united with global history now? Seems to be in line with how `auto-mode` changed its ways of handling rules to a cleaner `get-data` interface.
Would web-mode have a separate history, it should be kept in sync with
the global history, right? Wouldn't that be trickier?
What are the benefits of a separate web-mode history?
What's the link with auto-mode "get-data interface"?
- `make-buffer` and some other places in code rely on quite a stateful modifications of history tree. We need to make it safer and cleaner.
If the history tree is global, isn't it stateful by definition?
Can you provide an example?
- Almost all the session-handling code will be removed, but where do we store named sessions commands then? In history.lisp?
Keeping the 3 commands in session.lisp is fine.
But since they are exactly dealing with history, moving them to
history.lisp would work too (but do it in a separate commit then).
Another question: With this new global history, nodes without buffer are
deleted.
This means the global history gets automatically cleaned up on buffer
deletion, which was not the case before when our global history used to
persist everything until the end of time (or until the user called
`delete-history-entry`).
So what about making the automatic clean up an option disabled by default?
Also maybe make it conditional to a duration, e.g. only delete the entry
if it's older than 1 week.
|
This will make session less coupled to global browser state. When we store session (global history, actually), we won't capture the history of the incognito buffers anymore, because these will have only buffer-isolated history.
Sure.
Yes, this sounds fair. |
This will make session less coupled to global browser state. When we store session (global history, actually), we won't capture the history of the incognito buffers anymore, because these will have only buffer-isolated history.
OK, could you document this in your patch set? Thanks!
`web-mode` used to have a separate history to enable per-buffer tree-like history, while global one was stored as hash-table keyed by URL-strings. Now that we have a tree-like history everywhere, we get buffer-local history tree for free. We don't need separate history-handling in `web-mode` then.
So there is no benefits in having a separate history in web-mode, or is there?
`get-data` and `auto-mode` example is there because, like in #910, we turn a browser-global session into a (possibly) buffer-local.
How is can the session be buffer-local?
Do you mean that a buffer can individually decide whether it belongs to
the global history or not?
> If the history tree is global, isn't it stateful by definition? Can you provide an example?
Sure. `make-buffer` sets `htree:current` of history to `htree:root` for non-child buffers. If one creates a non-child buffer (call it A) in the background and then navigates to some page on current buffer (call it B), then the new history node for B will be inserted to the root of the tree instead of it's dedicated branch. No matter what happens with A, B's history is now in two places: in the root and its dedicated branch. This doesn't seem right.
Indeed. Off the top of my head, here is what I suggest: Make the
history as immutable as possible. This might require some
(interesting!) changes to the htree library.
- Instead of htree:current, store the current node buffer-locally.
- Only delete nodes in the the delete-history-entry command.
- Maybe leverage an immutable data structure like fset:seqs. This means
that when you delete a node it creates a new tree which you assign to
the global history tree variable. Parallel operations are thus
not impacted by node deletion.
- If you don't go for an immutable data structure, you'll need to
protect the htree accesses with read/write locks.
|
Another use-case question: What if the user wants to decouple the global
history from the list of buffers to restore (the session)?
1. I want to restore no buffers or other buffers but keep the same
global history.
2. Vice-versa: I want to restore the buffers but discard the global
history.
I think this question is the same as my previous remark about automatic
node deletion. If we don't delete history nodes when we delete buffer,
we get this decoupling between the buffer list and the global history,
which seems ideal.
Thoughts?
|
Done :)
There's no benefit anymore, now that we have tree-like history everywhere :)
In some sense. But what I meant by buffer-local session is that now we can isolate history in buffers (like we can isolate auto-mode-rules). Potentially, we can have several buffers that store their common history-tree to some buffer and then persist it to the session file. So, yes, session (in the sense of the composite history of several buffers) can become buffer-local too :)
I'm not sure how this
This is the way to solve the problem with these additional branches, I believe. Having buffers reference the parent buffers is an option too.
Again, I don't quite get how this could help the problem with unnecessary history modification after new buffer creation.
We lock data on its modification using |
Yes, that's a good use-case. What I see as options here are:
Adding to that, we can prompt the user for the list of buffers to restore. We have synchronous minibuffer now, so it shouldn't be a problem anymore. (By the way, we should use it in
Yes, that's fair. Deleting nodes seems to be a premature optimization that hurts one's experience more that it enhances it :) |
4e7f006
to
d1a4879
Compare
OK, I believe I've covered most of review points, but there's nothing else except that. I'll get to experiments and fixing CI/tests once I can run Nyxt again :) |
Performance question: the global history might grow large.
How fast can we flatten a global history of 10.000 entries?
Where can it slow down the browser?
One possible bottleneck is when we list the suggestions of set-url.
|
I suggest that we do not worry about the question of performance just yet. Let us first hit the wall, and then do some optimization at that time. There are many potential solutions, we can for example chunk the history to a running 30 day window. |
Artyom Bologov <notifications@github.com> writes:
> Indeed. Off the top of my head, here is what I suggest: Make the history as immutable as possible. This might require some (interesting!) changes to the htree library.
I'm not sure how this `htree:current` modification can be solved by immutability of a history. Can you expand on that, please?
I need to look at the code again. I'll come back to it later.
Having buffers reference the parent buffers is an option too.
Can you elaborate?
> - Only delete nodes in the the delete-history-entry command.
> - Maybe leverage an immutable data structure like fset:seqs. This means that when you delete a node it creates a new tree which you assign to the global history tree variable. Parallel operations are thus not impacted by node deletion.
Again, I don't quite get how this could help the problem with unnecessary history modification after new buffer creation.
Just a hunch but maybe it won't help.
> - If you don't go for an immutable data structure, you'll need to protect the htree accesses with read/write locks.
We lock data on its modification using `with-data-access` already, don't we?
Completely forgot about that! :)
So if we don't have any risk of race condition, how can the problem you
described occur?
|
Artyom Bologov <notifications@github.com> writes:
> Another use-case question: What if the user wants to decouple the global history from the list of buffers to restore (the session)?
> 1. I want to restore no buffers or other buffers but keep the same global history.
> 2. Vice-versa: I want to restore the buffers but discard the global history.
Yes, that's a good use-case. What I see as options here are:
- emptying the `history-entry` `id`-s of the buffers one doesn't want to save to session. This way one can restore the buffers they have previously chosen to save, and nothing else.
- Adding `restore-buffer-p` slot to `history-entry` (it's not optmal, because we'll need to set it for all the entries of the same buffer).
- Restrain from modifying `id`-s in `history-entry`-es, so that one could always pick the suitable set of buffers on session restoration. This require us to traverse the history for the maximum `id` to make sure new buffers don't collide with the ones already in history. This is sub-optimal.
Ideally IDs should always be generated. When we restore buffer, we do
like dead buffers and we make a new buffer with the given properties.
The ID is always generated.
And what about the second point: saving buffers but discarding the global history?
I think the answer is simple: we delete all history nodes. The global
history would then just have the nodes corresponding to the live buffers.
(By the way, we should use it in `auto-mode` now!)
Use what? Synchronous minibuffer? Sorry, I forgot what for!
|
To prompt the user with the list of modes to enable for a matching page. Because rules can have some modes user doesn't actually want to enable this time. |
Sure. If we store the parent buffer id/reference as a slot of child buffer, then we won't need to change the
It's not a race condition, it's improper data modification. Again, when new buffer is created, the Everything is properly locked and no race conditions occur, it's just that the data is modified in a way that can shoot one in the foot. |
Artyom Bologov <notifications@github.com> writes:
>> Having buffers reference the parent buffers is an option too.
> Can you elaborate?
Sure. If we store the parent buffer id/reference as a slot of child buffer, then we won't need to change the `htree:current` to start an independent buffer history -- we can just look up the parent buffer in its slot:
- If there is a parent buffer, then branch history out from the parent buffer's current history node,
- If there's no parent buffer, branch out from tree root.
It seems to be equivalent to my suggestion of storing the current node
(what htree:current points to) buffer-locally.
It's not a race condition, it's improper data modification. Again, when new buffer is created, the `htree:current` is set to `htree:root` so that this new buffer can start its history from root as an independent branch. The problem is that if we don't switch to the new buffer and continue to use the old one, the old buffer history will continue from the root too. This is not always the behavior we'd expect.
The problem seems to be that htree:current is global, while really it
should be local to the buffer. Wouldn't that solve the problem?
|
The last commit mixed up changes to libraries/htree and source/.
|
I wasn't able to load any URL, there are more issues with history-add (last point). |
9c8a515
to
3c8ee45
Compare
No, that's not the same. The purposes of these functions are quite different, in fact:
No need to remove
I believe it is, unless we turn history-tree into history-graph :) The notion of history-tree is dependent on node ordering. To get back Creating "duplicate" nodes for buffers may consume more computer
Another option I lean to is to have a
I'm not sure. If we don't make this check, we can modify a possibly A question to ask there is: how do we modify a truly empty history
That sounds about right, but currently we store only a flat hash-table It's radical to just discard all the history, but aren't we moving |
(mapcar #'traverse (next-traversal node))))))) | ||
(if include-root | ||
(traverse root) | ||
(apply #'append (mapcar #'traverse (next-traversal root)))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Serapeum has map-tree
and walk-tree
, can't we use them instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Serapeum functions walk the cons cells (because lists are trees too). To use them, we need to transform our trees (represented by history-tree
class) into lists. That will probably cost more than using a dedicated function, so there's no benefit in using these.
But sera:map-tree
hooked me too, so I might need to see it's source to get some inspiration :)
Creating "duplicate" nodes for buffers may consume more computer
memory, but it's worth the price:
- "Duplicate" nodes represent how the browsing happens and how
sessions need to be stored.
- These nodes are easier to traverse back and forth, given that it's
simple `htree:back`/`htree:forward` call.
- This preserves the behavior of `web-mode` buffer-local
history-tree. It had some duplication too, and it was a useful
duplication.
OK, but then I have a question: while we have an existing buffer A with
some history, when a new buffer B is created from
scratch, how is its history going to start from the root?
It seems to me that history-add will add the new history entry of B to
the first child of the current history node, which is what A is visiting.
Am I missing something?
> * buffer-local-history-clean:
> I suggest we only clean up the history from the `delete-history-entry` instead.
Another option I lean to is to have a `*browser*`-based switch for
whether to clean history.
Can you describe an example use?
> * set-current-buffer fails
> It fails to start because the (with-data-access history ...) in
> `set-current-buffer` binds history to NIL if there is no history file, or if
> started with the private buffer.
> Shouldn't we do a NIL check in with-data-access?
I'm not sure. If we don't make this check, we can modify a possibly
empty data. This can come in handy (`bookmark-add` uses it already),
but then all the data becomes non-obviously typed as `(or null
type)`.
This is not okay either. All in all, we do NIL checks sometimes, as a
price for flexibility, so why not stay on the flexible side there :)
A question to ask there is: how do we modify a truly empty history
(e.g. on the first Nyxt start)? If we do a NIL check, we won't be able
to modify it.
I don't understand how bookmark-add is making use of a possibly NIL
`bookmarks` database. Can you explain?
Since this is a common use case, what about extending `with-data-access`
to set the bound variable to a default if NIL?
A bit like `gethash` does.
Example:
```lisp
(with-data-access (history INIT-VALUE) (history-path buffer)
(setf (htree:current history)
(or (htree:find-data (make-instance 'history-entry
:url (url buffer) :id (id buffer))
history
:test #'equals)
(htree:current history))))
```
In the above, if there is no history then HISTORY will be bound to
INIT-VALUE when entering the body of with-data-access.
> * Backward compatibility:
> This change alters the content of both the session and the history. We need
> to include importers to avoid disrupting our users too much.
[...]
Importing history won't provide enough information to construct a
meaningful history-tree,
Not a problem: we can make a "flat tree": a tree of depth 1 where all
previous history entries are direct child of the root.
It's no a stable version yet, but loosing thousands of entries in your
history tree can be very annoying to our users.
We wrote this kind of importers before when we changed the format, we
can do it again ;)
By the way, did you know that the first element of the persisted history
/ session is the version number?
This is convenient to write importers since it allows you to do version checks.
|
The implementation is still in writing, so you might've found something I haven't noticed. What it should work like is that when current buffer is switched, current history node is changed too, so that the history of new buffer will start from the proper node. But then, what node should we use if new buffer has no history yet? There are several options:
(initial-history-node (htree:current (get-data (history-path (current-buffer))))
:type (or null htree:node)
:documentation "The history node that this buffer history starts from.
Used to make sure children buffers properly branch from parent buffers in global history.") As you can see, I've already picked a solution (and you actually proposed it a month ago), but I'm still searching for something more elegant :)
The
The way that
Yes, I thought about it too. One possible drawback is that going forward in history from the root will overwhelm the user with branches that they didn't explicitly create and/or restore from somewhere. Is that enough of an inconvenience? I'd say it is, because it's not mimicking the browsing that user have done. Losing history of thousand entries is not an option either...
Yes, that's smart :) |
d09168b
to
450eb0a
Compare
OK, I remember now. Off the top of my head, I think I decided to raise a
But what about
Sorry, I don't understand. Can you provide an example?
I think it's not a problem. Consider this:
Now in buffer three, if you go back twice, you'd get
But now that I think about it, it's not what we want to do because then we would Maybe our htree data structure is not shaped well for what we are trying to The problem looks to me like we need a nested history-tree:
htree would need to be updated to support adding parents above the "original node". This
Thoughts? |
I gave it a shot in 2fa8152. Is that enough of fix?
Oh, you mean that!
Regarding However, I can be terribly mistaken about what actions should branch out from the current node. It's too subjective. Maybe we can make it configurable?
Based on your example, if user goes back and history looks like that,
They can always switch buffer and
This way we won't occupy any more space in the tree while still preserving buffers' history, or at least
We had this idea of buffer-tree holding history-trees on the initial discussion stage xD This also sounds like one day But my main impression is that it's not that dramatic and we can handle these things with the current implementation of |
Artyom Bologov <notifications@github.com> writes:
Regarding `set-url-from-bookmark` -- the argument is the same -- we need to create child buffer to preserve the browsing order and user's mental picture of it.
However, I can be terribly mistaken about what actions should branch out from the current node. It's too subjective. Maybe we can make it configurable?
In my opinion, we should branch out only when
- Cloning a buffer (for which we don't have a command yet).
- Opening a link in a new tab (mouse click of link-hint).
All other commands would start from the root.
> Sorry, I don't understand. Can you provide an example?
Based on your example, if user goes back and history looks like that,
```
- (ID 3) (URL https://ambrevar.xyz/)
- (ID 3) (URL https://ambrevar.xyz/projects/index.html)
```
They can always switch buffer and `current-history-node` of buffer 2 (set to the node with "https://ambrevar.xyz") will be rewritten to have ID equal to 2, i.e.
```
- (ID 2) (URL https://ambrevar.xyz/)
- (ID 3) (URL https://ambrevar.xyz/projects/index.html)
```
This way we won't occupy any more space in the tree while still preserving buffers' history, or at least `current-history-node` that history can be expanded from. Done in f49a60f.
I'm confused: are you saying that the state of the tree depends on which
buffer is currently showing? If so, it seems problematic to me.
Also maybe you misunderstood the problem I was referring to: if you
restore the first history, you'll only have 1 buffer, while you had 2 initially.
We had this idea of buffer-tree holding history-trees on the initial discussion stage xD
I only have vague memory of this, sorry :p It seems that this is a bit
different though.
This also sounds like one day `history-tree` will become `history-graph` 🤔
But my main impression is that it's not that dramatic and we can handle these things with the current implementation of `htree`. It can always be rewritten later. Let's just try to debug this implementation and rewrite it in the future if we hit some `htree` grave limitations :)
It seems to me that it's fixable if we don't alter the history of other
buffers.
However, we need a way to expand a history from it's root, and we need
to have a slot that refers to the node from which the history was
branched out.
In particular, a buffer can jump to anywhere in the global history,
including to an unrelated branch. So if we expand this history, we
must be able to reconstruct the whole path from the root to the distant
node.
Another approach: instead of storing a single ID, store the a list of
IDs in the history entries. This involves a serious design change though.
I'll sleep over it.
|
Yes, that makes sense.
Yes, that's problematic.
The idea of storing all IDs is good, but restoring sessions will become somewhat random, because one EDIT: senSe. |
I've slept over it and came up with some (hopefully) useful insights! But first let me answer your remaining points.
Sadly not, but in any case I don't think this is the right approach: we should
Maybe we don't have to go down this (fragile) road if we keep the whole state.
I am not sure, I haven't studied this part well enough.
Sorry, you were right, you can't go back up multiple times if we insert nodes.
Should work, I'll test later. When I said:
maybe I was too opinionated and you are right, the decision of branching is
Thank you for mentioning that, I had missed this point! Draft coming up just now. |
Let's recap. What we want to keep in memory:
Potential issues we need to deal with:
For this last issue (3) I guess maintaining a hash-table like we have on master, Let me know if you can think of anything else. I think the core of the problem we are facing is due to us trying to use So I suggest we overhaul the library to turn it into an actual GHT library. To An owner is a unique object that "owns" (or visited) a node. In our case, The Finally, as mentioned in the previous post, the node children order should not This would give us the following data structures: (defclass node-info ()
((parent-owner :accessor parent-owner
:initarg :parent-owner
:initform nil
:type t)
(forward-child :accessor forward-child
:initarg :forward-child
:initform nil
:type (or null node)
:documentation "Which of the `children' (in a `node') is the
child to go forward to for this owner.")
(last-access :accessor last-access
:initarg :last-access
:initform (local-time:now)
:type local-time:timestamp
:documentation "Timestamp of the last access to this node by the
owner."))
(:documentation "Internal node of the history tree."))
(defclass node ()
((parent :accessor parent
:initarg :parent
:initform nil
:type (or null node))
(children :accessor children
:initarg :children
:initform nil
:type list
:documentation "List of nodes.")
(owner-node-info-map :accessor owner-child-map
:initarg :owner-child-map
:initform (make-hash-table)
:type hash-table
:documentation "The key is an owner, the value is a
`node-info'. This slot also allows us to know to which ownder a node belongs.")
(data :accessor data
:initarg :data
:initform nil
:documentation "Arbitrary data."))
(:documentation "Internal node of the history tree."))
(defclass owner-data ()
((origin :accessor origin ; TODO: Rename to `root'?
:initarg :origin
:type (or null node)
:initform nil
:documentation "The first node.")
(current :accessor current
:type (or null node)
:initform nil
:documentation "The current node.
It changes every time a node is added or deleted.")
(nodes :accessor nodes
:initarg :nodes
:initform (make-hash-table)
:type hash-table
:documentation "The set of all unique nodes visited by an owner."))
(:documentation "Owner information."))
(defclass history-tree ()
((root :accessor root
:initarg :root
:type (or null node)
:initform nil
:documentation "The root node.
It only changes when deleted.")
(owners :accessor owners
:initarg :owners
:initform (make-hash-table)
:type hash-table
:documentation "The key is an owner, the value is an `owner-data'."))
(:documentation "History tree data structure.")) This effectively remembers the whole state (all the points in the first list of Let me know if you see something missing. The node count should be as small as possible since the nodes are shared We can list the buffer history nodes without browsing the whole tree by 2 other challenges:
The benefits of moving the whole logic to this library is that the code will be Let me know what you think! |
Wow! That ticks all the boxes for me :) It also resembles UNIX filesystem access model. Coincidence or pattern 🤔 |
Well, the history tree also reflects how a _file manager_ keeps track of
its history.
This will be useful later when have implement one in Nyxt :)
Happy you like this design! There may still be some rough edges, but we
will see when time comes.
How shall we proceed? Are you getting tired of this patchset? If so,
don't hesitate to let me know, I can give a hand. For instance, I can
work on the history-tree library overhaul, while you'd integrate it in
Nyxt afterwards. You can also do everything or nothing, as you
prefer :)
Let me know!
|
Tinkering with |
No problem, let's do that!
I'll work on it as soon as possible.
|
I've pushed a draft of the overhauled history-tree library on the I've updated the naming and the slots a little bit compared to the above draft. Now I'm facing the main challenge: how do we handle owner and node removal? I took these notes:
So for now I'm leaning towards deleting the nodes when they are disowned. This loses some information, but I think it's saner, because practically how Bonus: Adding a stash of all known data (for us: URLs + titles) is a convenient So here are my questions:
Thoughts? @aartaka @jmercouris I need to sleep on it, I'll work on a draft tomorrow. Let me know if anything is not clear and I'll explain (maybe some example would |
Another question: how do we delete individual history entries? But for owned nodes (or disowned nodes that would still be in the tree if we Does that make sense? Otherwise, we could forbid owned node deletion, and only offer to delete Thoughts? |
Yes, I do like that! Reminds of smart Linux Kernel linked lists hack :) We will save some memory this way, and it will probably be faster.
It breaks the mental picture of history, but it seems to be the best solution given the way history is built. I'm still trying to wrap my head around what's on the branch, though. |
Artyom Bologov <notifications@github.com> writes:
I'm still trying to wrap my head around what's on the branch, though.
You mean the Git branch or the "history-tree" branch? :)
|
My first thoughts:
Given tree A->B->C If we delete B, then the new parent of C should be A.
|
I hope I have understood the problem/questions correclty :-D |
Thanks for your feedback!
+ I do not like the idea of storing the data outside of the nodes and in another structure, I find it confusing. If you want to have fast access to nodes and their attributes, you can maintain a set of pointers in parallel.
To clarify, this is an implementation detail of the history-tree
library. Nyxt would see nothing of this.
Your suggestion of "pointers in parallel" is actually what I'm
suggesting I think.
+ Instead of reparenting to the root, I would suggest reparenting to the parent of the deleted node
Given tree A->B->C
If we delete B, then the new parent of C should be A.
But does it make sense semantically?
Before we delete, the parent of C is B. This means that we browsed from
URL A, then to B, then to C.
If we reparent, it kind of "changes the past", and then if we look at
the list of parents we see that the parent of C is A, which would imply
that we browsed from A to C, which is not true!
To recap, I can see 3 options:
- Reparent to the nearest owned node (as above, C raparented to A).
Drawback: Loses some information (actual parent), rewrites the past.
- Reparent to the root (C is reparented to the root).
Drawback: Loses the most information, also rewrites the past.
- Don't reparent, keep all branch nodes until there is no single owned
node in the branch.
Drawback: Tree may remain relatively big until some buffer is deleted.
Benefit: Preserves the past, keeps all information.
I'm leaning towards never reparenting for now.
I think an immutable history is less confusing and more informative,
even if it leads to showing more nodes when browsing dead parents /
children.
+ how do we delete individual history entries? (options)
I think we should forbid it.
Yes, leaning towards this as well.
Now the question is how do we clean up the history when the branches are
immutable? This can only happen if the history entry is only in the
stash and not on any branch.
So what if the user wants to remove a "dead" entry (not owned by any
buffer) that's still on a branch?
What about adding a `detach-history' command which resets buffer
histories by creating a new branch of all the owned nodes? This would
effectively detach the history for this buffer, thus possibly freeing
the bigger branch it was relying on, along with its dead nodes.
We could add multiple-selection to this command, thus allowing to detach
the histories of all buffer, which would effectively free all the dead
nodes. Then these nodes can be deleted from the stash.
|
I thought about the same thing, but more in terms of mode for history isolation. Mode will make it visual that the history of the current buffer is not connected to the main one, and it will also hide all the detachment details behind something simpler, like |
I am not sure about modes, because what happens when we deactivate the mode I think a command would address all your concerns:
But maybe I misunderstood what you meant. |
Yes you do! I agree with all the points, especially the one about mode deactivation. |
Thanks for your feedback!
|
I think I've gotten around all the design issues (maybe minus the thread-safe I've started testing. |
I'm almost done with the history-tree overhaul! What remains to be done:
This is not urgent though and Nyxt can already make use of the new API (it's @aartaka: Would you like to try to integrate it in Nyxt? To go forward, we can either keep working on the global-history-tree branch, or A few questions, for @jmercouris and @aataka:
|
Sorry, it seems I've underestimated my time and workload with life and QtWebEngine :) Wouldn't you mind integrating it yourself? It's also going to be faster, I guess.
The choice is yours, but I agree that the current pull request is uncomfortably long.
Regarding
Maybe we can make it a method dispatching on either tree, node, or owner? In the latter case it can search for owned nodes only.
Maybe utilizing methods here too:
|
Artyom Bologov <notifications@github.com> writes:
> @aartaka: Would you like to try to integrate it in Nyxt?
Sorry, it seems I've underestimated my time and workload with life and QtWebEngine :) Wouldn't you mind integrating it yourself? It's also going to be faster, I guess.
No problem.
I won't be able to work on it this week though, as I have to prepare for FOSDEM ;)
Next week then!
The choice is yours, but I agree that the current pull request is uncomfortably long.
OK, then I'll rebase, start a new branch, pick select commit from yours
and send a new pull request.
Regarding `history-tree` vs. `history`: I'd leave `-tree` there, because it's more of a data-structure library/class than just history-management one. Regarding `(shared)|(global)-?history-tree`: Not sure about that, but staying with `history-tree` sounds fine.
OK, I'll just stick with what we have unless John has some other preferences.
> * Should we have different functions for finding nodes vs. "owned nodes",
> or pass an option as key argument?
Maybe we can make it a method dispatching on either tree, node, or owner? In the latter case it can search for owned nodes only.
> * Same question for visiting "unowned" nodes: shall I add an extra argument to
> `forward` and `back`, or shall I make `forward-unowned` and `back-unowned`?
> If the latter, suggestions for better names? Maybe rename `forward` to
> `forward-owned` instead, and call the "unowned" version `forward`?
Maybe utilizing methods here too:
- if it's a tree, then don't consider ownership
- if it's an owner, then consider ownership
I had initially gone with methods, only to realize they don't quite fit
the job.
Indeed, for a given owner, you may want to list the owned children _or_
all children. So we can't decide what to do based on specialization
alone.
Then I realized that specialization on owner was duplicating the one on
history, so I removed it. Now we only have methods/functions on history
(and a few on nodes, but that's for the lower level stuff).
|
I have no preferences, the names are OK with me in either case :-) |
Closing this then? |
This changes the history to a more coherent and buffer-oriented model, turning browser history into one huge tree by default.
Motivation
This change is to make history handling coherent with recent changes (#910) and to open a way for new features (#247 (when we store session, we won't capture the history of the incognito buffers anymore, because these will have buffer-isolated history), #854, #958).
Adding to that, there is a change in user experience -- buffer-local histories are now connected to each other and do branch out from each other. This means that if you open a link in a new buffer, you'll be able to return to original page even in this new buffer. It's not conventional, but it's productive, and there are examples of extensions to other browsers (Firefox and Chrome) that organize tabs/histories into a tree for the sake of productive navigation.
What's New
(get-data (history-path (current-buffer)))
) is ahtree
.buffer-description
is deleted in favor ofhistory-entry
.web-mode
relies on the global history, instead of having its own.map-tree
,do-tree
,remove-node
,find-node
,find-data
,delete-data
,add-children
) are added to thehtree
code.(re)store
methods can have arbitrary keyword arguments.history-tree
command to see the full tree in it's grotesque beauty xDhistory-tree
andbuffer-history-tree
are now colored/glyphed (withdisplay-buffer-id-glyphs-p
set to non-nil) to highlight the current buffer.history-list
to see the history as a list.web-mode
configuration slots for history navigation:history-forwards-prompting-p
-- whetherhistory-forwards
prompts the user on which branch to take in case there are several. Defaults to t.history-forwards-to-dead-history-p
-- whetherhistory-forwars
considers the dead history nodes worth visiting. Defaults to nil.conservative-history-movement-p
-- whether history nodes that don't belong to the current buffer are considerend visitable, i.e. conservative buffer-bounded navigation that we had before the patch. Defaults to nil.Caveats
htree
?How Have This Been Tested.
How does that look to you?
EDIT: Add a note about how this helps #247.
EDIT: Update it with the recent changes as of 11/21/20 (
map-tree
, no more history modification on buffer switching).EDIT: Mention history and session importers.
EDIT: Mention runnability and bugs.
EDIT: Update with the latest changes (internal buffer URLs,
add-children
,(re)store
keywords).EDIT: No more internal buffer URLs and hangs on
history-backwards
.EDIT:
history-tree
.EDIT: Colors,
history-list
, promptinghistory-forwards
.EDIT:
history-forwards-prompting-p
,history-forwards-to-dead-history-p
, moving things toweb-mode
.EDIT: Session is not restorable.
EDIT:
find-node
,remove-node
.