joostkremers / ebib

A BibTeX database manager for Emacs.
https://joostkremers.github.io/ebib/
BSD 3-Clause "New" or "Revised" License
272 stars 37 forks source link

Commands to support other navigation paradigms #270

Closed Hugo-Heagren closed 2 weeks ago

Hugo-Heagren commented 1 year ago

I often find myself adding a new bib entry to my file, only to realise that I need to add a supporting entry of some kind first (like a Collection, which I can refer to from an InCollection). If the two have different principle authors they won't be very close in my index, so its a hassle to move between them.

I think it would be useful to add two features to deal with this and similar problems:

  1. Commands for jumping through the history of which entries one has visited.
  2. Commands similar to point-to-register and jump-to-register, for storing and jumping to an entry's location. Those commands already kind of work for jumping between entries in the index, but because they are based on the location of point, they can't deal with jumping to an entry after changing the sort order, or adding a new entry. My proposed commands are based on jumping to an entry with a given key (the history commands would work similarly).

This PR:

A few notes on my history implementation My code is slightly modified version of the stack/head system described at https://gist.github.com/CMCDragonkai/d266a3055735545447439f0fa662a0e1 . In my implementation: - there is a list for the history (`:history` in `ebib--entry-history`). Elements nearer the front of the list are more recent in history. - there is a head pointer (`head` in `ebib--entry-history`). This is a number, a 0-based index into the history list, of where we are currently in the history. - to move back in history: - look up the entry before our current position in history (whatever is at `history-list[head+1]`). Error if there is no such entry. - goto that entry. - increment head - Importantly, going back does not mutate the history list. - to move forward in history: - look up the entry after our current position in history (whatever is at `history-list[head-1]`). Error if there is no such entry. - goto that entry. - decrement head - Importantly, going forward does not mutate the history list. - When doing anything else (jumping to an entry, going to the next/prev/first/last entry etc.) (in order): - take everything from the second to the `head`th element inclusive of the history list, reverse what you've got, and prepend that to the list. - prepend the entry we are going to to history - set head to 0 (we are at the end of history so far) So for example... We open a new database, at entry `@a`. This is pushed to the history list, so now `history` = `("a")` and `head` = 0. We move to next entry `@b`. `history` = `("b" "a")`, `head` is still 0. next entry again to `@c`, then `@d`, then `@e`. After this, `history` = `("e" "d" "c" "b" "a")`, `head` is still 0. Notice how the history list is reversed compared with our experience (we visited `@a` first, but "a" is last in the list). We move back in history (M-xebib-history-back). The current entry is now `@d`, `head` becomes 1, but the history list is unchanged as `history` = `("e" "d" "c" "b" "a")`. Move back again. Current entry is `@c`. Now `head` = 2, `history` is still `("e" "d" "c" "b" "a")`. And back again. Now `head` = 3, current entry is `@b`, `history` is still `("e" "d" "c" "b" "a")`. (Notice how `(nth head history)` is always the same as the key of the current entry.) We now jump to entry `@f` with `ebib-jump-to-entry`. head is 3, so everything from the second history to head will be the things at index 1, index 2 and index 3 (head), which is `("d" "c" "b")`. Reverse this list and prepend to the history list: now `history` = `("b" "c" "d" "e" "d" "c" "b" "a")`. Then add the entry we are jumping to: `history` = `("f" "b" "c" "d" "e" "d" "c" "b" "a")`. Finally, set `head` = 0 (once again, notice that we are at entry `@f`, and element of history at index `history` is "f"). To briefly demonstrate why this makes sense: going back through the description I have just given, a list of entries we have actually visited (in the order we visited them) would be: `a b c d e d c b f`. We are now at `f`. If we go back lots of times, our experience ,*should* just go back along that list. And looking at the `history` list, it will: `history` is just that list backwards. Currently there is only one history list, used for a whole ebib session, across multiple databases. Entries are located with their key and the current db, by `ebib--find-db-for-key`. I did consider building separate lists for different databases but: - this would have been more work - personally, I prefer one master list, so to implement separate lists I would really have had to implement both versions, and control which was used with a user option. This would be fine in terms of user experience, but it would be a *lot* more work, and (since nobody has built this before anyway) I feel there probably isn't the demand for it to be worth it. Currently, history persists between ebib sessions, so if I open database A, do some stuff, then quit ebib and later open B, my history from A will still be lying around. I'm not sure if this is a problem---it doesn't really effect the experience too much (and I for one mostly use a single huge database anyway), but I can see why some might not like it. What do you think?
joostkremers commented 1 year ago

This looks pretty cool!

One question (which may be answered by the page you linked to, but I only skimmed it...): If you go back through the history, then go forward again, and then backward again, followed by a jump to another entry (i.e., not in the history), this to and fro is not reflected in the history, right? Not that I mind, I was just wondering.

So is this ready to merge, or do you want to test it yourself for a while first?

Hugo-Heagren commented 1 year ago

This looks pretty cool!

Thanks :smile:

One question (which may be answered by the page you linked to, but I only skimmed it...): If you go back through the history, then go forward again, and then backward again, followed by a jump to another entry (i.e., not in the history), this to and fro is not reflected in the history, right? Not that I mind, I was just wondering.

I'm not sure what you mean by "to-and-fro", but I can try to answer. If you go back, then forward, then back, then jump to another entry, every entry you have been to (in any way) will be in the history record, but the back-forward-back pattern will not be recorded. A consequence of this is that the history record will look the same after any of the followign sequences:

This was a concious decision. I wanted the user to be able to go back and fortrh throgh the history as much as they liked, without altering the history record itself (this is because the implementation gets really complex once you start allowing history-movement to also record to the history list).

In general: there is no perfect way to record history which also allows you to move around in the history (because then you have to decide whether and how moving around will itself affect the history record). Everything is a bit of a tradeoff between sophistication and useabillity.

The most powerful is a full branching graph model like git, but that seemed a bit much for ebib (in terms of use and implementation). The least sophisticated is just a list, but the problem with that is if you back, then go to a new entry (without going forward again) you lose everything you did before you went back (most browser and word processor history implementations suffer from this). I tried to pick something in the middle: in my implementation, no state is ever lost -- if you visted an enty once (anywhere in the history) and you go back enough times, you will get to it. This is similar to emacs' own undo system, and has the same flaw: a lot of state has to be recorded, so if you use the back/forward commands a lot, often you have to go back quite a lot before you find what you want. I think that's alright though.

So is this ready to merge, or do you want to test it yourself for a while first?

I think it's ready to merge.

joostkremers commented 1 year ago

I'm not sure what you mean by "to-and-fro",

I meant exactly what you answered, so :+1: . I also think the reasoning makes total sense,

I think it's ready to merge.

Cool! Will do so some time today or tomorrow.

abdalazizrashid commented 3 weeks ago

Any updates on this?

Hugo-Heagren commented 3 weeks ago

Any updates on this?

There are some conflicts with master branch (should show up on github if you look at the PR) and I don't have the time right now to go through and fix them.

joostkremers commented 3 weeks ago

Well, I tried to resolve those conflicts (there weren't many), but now for some reason all the commits made between this PR and the present suddenly show up here... And Github says the conflict still exists, even though my local copy doesn't have it anymore.

This is one of those times where I realise I don't really understand Git at al... :fearful:

Hugo-Heagren commented 3 weeks ago

k I'll have a look at this today

joostkremers commented 3 weeks ago

k I'll have a look at this today

No, don't bother. I've cherry-picked your commits onto master. I'll be running some quick tests and push them to Github tonight.

Hugo-Heagren commented 3 weeks ago

No, don't bother. I've cherry-picked your commits onto master. I'll be running some quick tests and push them to Github tonight.

Oh! Well I only saw this once I came back to github after resolving the conflicts. Oh well...

joostkremers commented 3 weeks ago

Pushed.

I did note a few things, though. First, I couldn't really navigate forward with C-f: after moving back through the history with C-b a few times, I'd expect to be able do the same number of C-f, but it always stopped after hitting it once. Hitting it again resulted in the error "Nowhere to go". Am I doing / understanding something wrong?

Looking at the history list in edebug, I noticed that there were a lot of duplicate entries. This is what I get when evaluating ebib--entry-history:

(:head 0 :history (#1="Abney1991" #1# #1# #3="Abney1987" #2="Abels:Neeleman2009" #2# #2# #2# #2# #2# #2# #2# #3# #1# #4="Abraham1982" #4# #1# #3# #2# #7="Abels2004" #6="Aarons:Bahan:Kegl:Neidle1992b" #5="Aarons:Bahan:Kegl:Neidle1992a" #5# #5# #5# #6# #7# #2# #3# #1# #4# #4# #1# #3# #2# #2# #8="Chomsky1986" #8# #9="Zybatow:Weskott2018" #8# #8# #9# #8# #8# #2# #2# #3# #1# #4# #4# ...))

Is there a way to prevent identical adjacent elements?

Hugo-Heagren commented 3 weeks ago

I think this is to do with the way I mutate the history -- I'll have a look and try to come up with a fix.

Hugo-Heagren commented 3 weeks ago

302 should fix the problem. For some reason my code for moving forward in history didn't follow my own proposed implementation logic.

Hugo-Heagren commented 2 weeks ago

I take it I can just close this now, as the commits have been merged separately?

(btw, I used this feature in anger today and it was great!)