radian-software / prescient.el

☄️ Simple but effective sorting and filtering for Emacs.
MIT License
603 stars 25 forks source link

Context-aware prescient-forget #118

Closed agenbite closed 2 years ago

agenbite commented 2 years ago

I'm trying prescient-forget and I guess there's something I don't get about the inner workings of prescient.el. For example, if I call find-file, I get a nice list of files. Then, if I call consult-buffer, I get an even nicer list of buffers and other things. However, if I call prescient-forget on any of the two prompts, I get a list of candidates to forget which mixes up candidates from both find-file and consult-buffer. In fact, I get candidates from other prompts as well, which makes it hard to know what's what.

So, I guess my question is if there's a way to get a contextually relevant list of candidates upon calling prescient-forget.

okamsn commented 2 years ago

I'm trying prescient-forget and I guess there's something I don't get about the inner workings of prescient.el. For example, if I call find-file, I get a nice list of files. Then, if I call consult-buffer, I get an even nicer list of buffers and other things However, if I call prescient-forget on any of the two prompts, I get a list of candidates to forget which mixes up candidates from both find-file and consult-buffer. In fact, I get candidates from other prompts as well, which makes it hard to know what's what.

I'm not sure what you mean by "on the prompt". prescient-forget can be run using M-x prescient-forget. You don't need to run another command beforehand, if that's what you are referring to.

Prescient works by recording candidates selected by completion UIs like Company or Selectrum. The frequencies of selected candidates are recorded in one table and the recency of selected candidates in another.

These two tables are used for all selected candidates for commands. The candidates are not currently separated by which command produces a list of candidates or in which history variable (those outside of Prescient) a candidate is recorded. This lack of separation means that a candidate's ranking applies across commands. For example, if one reads a candidate's documentation using describe-function, that candidate will be suggested when completing function names using Company.

prescient-forget presents the candidates recorded in the tables, so it shows candidates used in all commands. Forgetting a candidate means deleting the ranking from the tables, which applies to all commands.

So, I guess my question is if there's a way to get a contextually relevant list of candidates upon calling prescient-forget.

Not at the moment, no. The sorting does not consider context. This was requested in #85, but it hasn't been implemented yet.

agenbite commented 2 years ago

On Fri Mar 11, 2022 at 6:15 PM CET, okamsn wrote:

I'm not sure what you mean by "on the prompt". prescient-forget can be run using M-x prescient-forget. You don't need to run another command beforehand, if that's what you are referring to. Exactly. I was thinking that I could do something like editing the list of candidates "on-the-fly", so to speak, and now I understand that the edition needs not be at the same time. Thanks for your detailed explanation.

Not at the moment, no. The sorting does not consider context. This was requested in #85, but it hasn't been implemented yet. Good to know. I'll keep an eye on that.

Thanks again! :)

agenbite commented 2 years ago

I'll add here that, apparently, it is not possible to use prescient-forget to edit the history of candidates that one gets with M-r in Selectrum (from the docs: "M-r will invoke an improved version of history search with completion"). I believe it'd be great to have a single coherent way to access and edit the candidate history.

okamsn commented 2 years ago

I'll add here that, apparently, it is not possible to use prescient-forget to edit the history of candidates that one gets with M-r in Selectrum (from the docs: "M-r will invoke an improved version of history search with completion").

Yes, selectrum-select-from-history is just the normal minibuffer history. The minibuffer history accessed with M-p and M-n is a normal Emacs feature that is unaffected by Prescient.

I believe it'd be great to have a single coherent way to access and edit the candidate history.

Try the below for editing the completion history variable. It seems to mostly work for me on Emacs 28.0.92.

consult-completing-read-multiple seems to work fine with it, but selectrum-completing-read-multiple is a bit odd when selecting multiple file paths. It can always be changed to normal completing-read.

(require 'seq)
(require 'selectrum)
(defun selectrum-remove-cands-from-hist ()
  "Remove candidates from hist.
Must be run inside command.  All instances of candidates are removed."
  (interactive)
  (let* ((selectrum-should-sort nil)
         (hist (symbol-value minibuffer-history-variable))
         (cands (completing-read-multiple "Candidates: " hist
                                          nil t nil t)))
    (set minibuffer-history-variable (seq-difference hist cands))))

(define-key selectrum-minibuffer-map (kbd "C-c d")
  #'selectrum-remove-cands-from-hist)
agenbite commented 2 years ago

Oh, thanks, @okamsn!! I've tried your code on Emacs 27.1 and, unfortunately, I get "symbol's function definition is void: t". Might that be related to my Emacs version?

EDIT: I've updated to the last Emacs master and I have the same result. What I'm doing is calling selectrum-remove-cands-from-hist after doing M-r in a prompt (for a notmuch tagging operation, if that's relevant).

okamsn commented 2 years ago

Try using it after M-x or C-x C-f (though note the quirk mentioned above). It won't work for commands that disable their history, like selectrum-select-from-history. History is disabled by passing t as the name of the history variable, which I think is the source of the error you see. I'll update the example in a bit.


EDIT:

Please try this version of the example command:

(require 'seq)

(defun selectrum-remove-cands-from-hist ()
  "Remove candidates from hist.

Must be run inside a command like `find-file' or
`execute-extended-command'.  All instances of candidates are
removed.

This command only edits Emacs's history variables.  It does not
affect features like prescient.el.  For that, see `prescient-forget'."
  (interactive)
  (cond
   ((eq t minibuffer-history-variable)
    (user-error "No history for this command"))
   ((not (boundp minibuffer-history-variable))
    (user-error "History variable not bound: `%s'" minibuffer-history-variable))
   (t
    (let* ((selectrum-should-sort nil)
           (hist (symbol-value minibuffer-history-variable))
           (cands (completing-read-multiple "Candidates: " hist
                                            nil t nil t)))
      (set minibuffer-history-variable (seq-difference hist cands))))))

(define-key selectrum-minibuffer-map (kbd "C-c d")
  #'selectrum-remove-cands-from-hist)
agenbite commented 2 years ago

Thank you for your patience and your help. It is hard for me to understand how this software works, and I don't want to sound as if I was expecting a solution to be developed for me. With this in mind, what follows is valuable only if it helps others and me to learn more about Prescient and Selectrum.

The solution you proposed does not really work in my case. If I do M-x and then C-c d, I can indeed select a candidate for removal. However, when I go back to the M-x selection list, that candidate is still there (it is not to be found if I call C-c d again, so it has in fact been removed somehow from somewhere). Surprisingly, if I cancel the selection at that point and hit M-x again, I get the "removed" candidate on top!

The situation is similar if I try to edit the list of frecently applied tags in notmuch, which I believe uses completing-read-multiple. Furthermore, in this case, if I try to delete a candidate after having used Selectrum's M-r functionality to get a list of the multiple tag candidates history, nothing happens.

Is there a way to being able to remove candidates from the very list I'm actually seeing when I call the selectrum-remove-cands-from-hist function? Would it maybe be possible to use embark to get that list into a buffer which one can edit, for example?

Thanks again! :)

okamsn commented 2 years ago

Thank you for your patience and your help. It is hard for me to understand how this software works, and I don't want to sound as if I was expecting a solution to be developed for me. With this in mind, what follows is valuable only if it helps others and me to learn more about Prescient and Selectrum.

There are two things:

  1. Emacs recording submitted candidates in the minibuffer.
    • Emacs does this by default.
    • The candidate is added to the front of the history variable. This means that using M-p in the minibuffer will suggest the most recently used previous candidates first.
    • The history can be specific to each command. Some commands have no history, and others use the generic minibuffer history (minibuffer-history).
    • This is what is presented when using selectrum-select-from-history.
    • These variable are what are edited when using the example selectrum-remove-cands-from-hist.
      • I've assumed that the variable is re-accessed every time M-p is used. That seems to be correct according to a quick check.
        1. Prescient recording (in the README's terms) the selection (TAB) and submission (RET) of candidates in supported completion UIs, such as Selectrum and Company.
      • The frequency and recency of a candidate are recorded.
      • These statistics are used to sort the list of candidates presented by a command, unless a sort order is already specified by the command.
      • These statistics are currently global, not command-specific. That is, there is only one calculated statistic for each recorded candidate, regardless of the command being used.
      • Not all candidates that a command presents will be in Prescient's tables, only the used candidates.

Prescient is for sorting lists of candidates (and filtering, of course). It is a similar but separate feature from the minibuffer history recorded by Emacs. There are times when one might wish to use a previous candidate via M-r that was not used recently or frequently. Having both allows this. For example, I have prescient-history-length set to 100 but the normal history-length set to 200.

Does this explanation make sense?

The solution you proposed does not really work in my case. If I do M-x and then C-c d, I can indeed select a candidate for removal. However, when I go back to the M-x selection list, that candidate is still there (it is not to be found if I call C-c d again, so it has in fact been removed somehow from somewhere). Surprisingly, if I cancel the selection at that point and hit M-x again, I get the "removed" candidate on top!

This behavior is the difference between the minibuffer history and Prescient sorting.

The process was this:

  1. Selectrum used Prescient to sort the list of commands.
  2. A command was removed from Emacs's list of previously used commands.
    • To do this, the command was selected. Prescient records this selection/submission, as it does for every selection.
  3. When M-x is next used, the command that was removed from Emacs's minibuffer history is sorted to the top by Prescient, as it was a recently used candidate in another command (in selectrum-remove-cands-from-hist).

This raises the question of what is meant by "removal". For Emacs's minibuffer history, that means deleting the candidate from the list variable. For Prescient, that can mean removing the candidate from Prescient's records, or, in other words, forgetting about the candidate.

However, Prescient forgetting about a candidate does not remove a candidate from the candidate list. It only sorts the candidate lower in the list, treating it like every other unrecognized candidate. Prescient's sorting method does not add or remove candidates from the candidate list, it only sorts them. The list of available candidates comes from the command itself, such as M-x or find-file.

For example, if I use a command which allows arbitrary input, that arbitrary input will be recorded by Prescient as being recently used, but it will never be sorted near the top of a list of candidates unless it already occurs in that list. It will, however, be added to the front of the history variable and will show up when using M-r with that command.

The situation is similar if I try to edit the list of frecently applied tags in notmuch, which I believe uses completing-read-multiple. Furthermore, in this case, if I try to delete a candidate after having used Selectrum's M-r functionality to get a list of the multiple tag candidates history, nothing happens.

If you mean that you tried to edit the history within M-r, that won't work. Because selectrum-select-from-history is itself a completing command, it has its own (disabled) history, which stops selectrum-remove-cands-from-hist from knowing which history variable was used by the command from which selectrum-select-from-history was called. You saw the effects of this earlier in the error about t, which selectrum-select-from-history passes as the name of its history variable (disabling history for itself).

Is there a way to being able to remove candidates from the very list I'm actually seeing when I call the selectrum-remove-cands-from-hist function?

The candidates that are listed by the example selectrum-remove-cands-from-hist are the same that are listed by selectrum-select-from-history. It's just that the former doesn't work within the latter, due to the reasons described above. The name of the history variable is reset for each recursive completion command.

Would it maybe be possible to use embark to get that list into a buffer which one can edit, for example?

I've tested it using embark-export and it does seem to edit the history variable as expected, though the list of items isn't updated in the Embark buffer. I'm not familiar with Embark enough to know how to change that.

In summary:

If you're wondering, selectrum-select-from-history does not use Prescient for sorting. The candidates are displayed as they exist in the history variable.

Does this answer your questions?

agenbite commented 2 years ago

Wow. Thank you so much for such a detailed answer. I'll read it slowly. It is indeed helpful.

agenbite commented 2 years ago

After carefully reading your answer, yes, it answers my questions very well. Thanks again! :)