1

Is it possible to make the first history item available as the default argument when running commands like eval-expression and goto-line?

I'd like to be able to just press enter to use the previous value.

I started writing a wrapper around goto-line but thought there must be a more general solution.

To be clearer, here's an example:

  1. I run goto-line, type 236 and press enter.
  2. I edit around for some time.
  3. I run goto-line again and I want to just press enter to go to line 236.

I use helm for M-x and C-x b, but there does not seem to be helm support for these other commands.

Matt
  • 15
  • 6
  • Are you aware of [savehist-mode](https://emacs.stackexchange.com/questions/9925/persistent-shell-command-history)? Then you can get the last command by pressing arrow up. AFAIK, [vertico](https://github.com/minad/vertico), by default, always selects the 'last in history' as first candidate (so that it would give you the functionality you ask for). I guess if you'd like to write functionality yourself, that you should probably look at the `minibuffer-setup-hook` and the `recentf-list`... – dalanicolai Dec 15 '22 at 15:31
  • Thanks @dalanicolai. savehist seems to be only for saving across sessions. vertico looks like it drops down the history under the prompt, but I want the prompt to stay at the bottom. – Matt Dec 15 '22 at 15:55

2 Answers2

1

[I edited the tags and title: The question is not about the prompt; it's about providing a default value when reading from the minibuffer. That the default is generally shown in the prompt is something else. If no default value has been provided then none will appear in the prompt.]

Whether there's a default value (or several), and what it is, is determined by the read function call (read-from-minibuffer, completing-read etc.), i.e., by the programmer who wrote that call. While reading, it's available in variable minibuffer-default (the value is put there by read-from-minibuffer C code).

You could conceivably advise some basic read functions to add your own chosen default value, but why?

What default value would you want to add, when there isn't one? And when: during the read? And how: interactively or with Lisp? Do you want to impose your own default value, regardless of what's being read? If so, on what basis would you pick that value? Exactly what behavior are you looking for?

If the read function call doesn't provide a default value then you should consider that to be by design (the programmer's choice). You can, however, send a suggestion to the maintainer of the code that makes that function call, suggesting provision of a default value.


On the other hand, you could add one or more values to the value of minibuffer-history-variable using minibuffer-with-setup-hook. Then you could use M-p to access those values. But you'd either have to do that for an individual read function call or you'd have to advise such a read function.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • Thanks Drew. At least for `goto-line` and `eval-expression` I want the default to be the first item in the history. Why, because I'm super annoyed having to type so much ;) It's fine if it's hacky, it just needs to work for me. Will look closer at advising functions, was hoping someone would have done this already. – Matt Dec 15 '22 at 17:05
  • You can always use `M-p` to insert elements from the history list. If you're asking now how to get the first element of the history list to be displayed in the prompt, that's something else. You can pose that as a separate question. (I don't have a good answer for that, but maybe someone else does.) – Drew Dec 15 '22 at 17:44
  • I think I've mixed up the "default" concepts used by `read-number` and `read-from-minibuffer` which has made my question confusing. I'm trying to save typing `M-p`. Basically looking for this type of behaviour `(read-number "Goto line: " (read (car minibuffer-history)))` with some checks that there's a number on the history. I'll have to come back to this. – Matt Dec 15 '22 at 18:14
  • In any case, please don't "evolve" your question, moving the goal post, especially if there are answers. You can delete the question and pose a different one, however. – Drew Dec 15 '22 at 19:08
  • Maybe I phrased it badly but I did not "evolve" the question. It's the same goal post from the beginning: "I'd like to be able to just press enter to use the previous value." – Matt Dec 15 '22 at 20:04
  • No, it's not the same question, if you change "default value" to "previous value'. – Drew Dec 15 '22 at 20:57
  • That's a quote from my original text. It was "previous value" in that sentence all along. I did not change anything. I only added info to try make the question clearer. – Matt Dec 16 '22 at 06:50
1

I am not sure if this will cause any problems when calling some other functions (it is pefectly save to try it), but as mentioned by Drew already, you could advise the 'underlying' functions. For goto-line the following advice should do the trick:

(defun goto-line-advice-default-last (orig-fun &rest args)
  (when-let (hist (nth 2 args))
    (setf (nth 1 args) (string-to-number (car (eval hist)))))
  (apply orig-fun args))

(advice-add 'read-number :around #'goto-line-advice-default-last)

(note that the default value is displayed in the prompt, but/so you can just press RET)

And for eval-expression, the following advice should do it:

(defun eval-expression-advice-default-last (orig-fun &rest args)
  ;; only for eval-expression (so this is configurable)
  (when (string-match-p (car args) "Eval: ")
    (when-let (hist (nth 4 args))
      (if (eq (length args) 5)
          (add-to-list 'args nil t (lambda (a b))))
      (setf (car args) (format "Eval [%s]: " (car (eval hist))))
      (setf (nth 5 args) (car (eval hist)))))
  (apply orig-fun args))

(advice-add 'read-from-minibuffer :around #'eval-expression-advice-default-last)

You can configure where the advice applies via the first conditional (here I am using a when but you could also use an unless, see also the edit history for earlier versions).

Of course, you could additionally remove the item(s) from the history. If you really want that, I'll leave it for you as a nice little lisp exercise :)

Matt
  • 15
  • 6
dalanicolai
  • 6,108
  • 7
  • 23
  • 1
    You mean `(advice-add 'read-number :around #'goto-line-advice-default-last)`, right? – NickD Dec 15 '22 at 21:25
  • Right! Probably a few too many undo's before copy pasting... thanks again! – dalanicolai Dec 15 '22 at 21:29
  • Thanks @dalanicolai, that helped. This does what I want: ```(defun goto-line-advice-default-last (orig-fun &rest args) (unless (nth 2 args) (let ((hist (car minibuffer-history))) (and hist (or (eq hist "0") (> (string-to-number (car minibuffer-history)) 0)) (setf (nth 1 args) (string-to-number (car minibuffer-history)))))) (apply orig-fun args))``` – Matt Dec 16 '22 at 06:43
  • I guess I'm better off making wrappers around these functions that handle the input. Especially for `eval-expression` which will have to access `command-history`. – Matt Dec 16 '22 at 06:45
  • I am not sure what you mean by that last comment. Of course, the advice already wraps around the functions, you can just modify it to your needs. The answer provides a solution to your question as asked (as I don't know your further requirements). Also, I am not sure why you'd need `command-history` instead of `read-expression-history` (the `(nth 4 args)`) here. Maybe you can explain what does not work about the answer? – dalanicolai Dec 16 '22 at 08:35
  • B.t.w. I see that I left a little mistake in `eval-expression-advice-default-last` as the 1 in `(nth 1 args)` should have been a 5 (it should be a default value instead of initial value). I guess, that would be the solution you are looking for. However, when changing it to 5 then the function does not work, but I don't know why. To me it looks like a bug, but I guess we should ask that here, or in the Emacs [bug-gnu-emacs](https://lists.gnu.org/mailman/listinfo/bug-gnu-emacs) mailing list. Or @NickD @Drew @any other members, are you reading this? Any idea/fix? – dalanicolai Dec 16 '22 at 09:24
  • I think it gives an error with 5 because `eval-expression` is only passing 5 args to `read-from-minibuffer`. If I extend args to 6 elements when it's 5 then it works. – Matt Dec 16 '22 at 15:21
  • And maybe your goto-line does not work for me because I'm on Emacs 27.1? That made me think I'd have to use `command-history` in `eval-expression-advice-default-last` like I did in `goto-line-advice-default-last`. – Matt Dec 16 '22 at 15:39
  • I edited your `eval-expression-advice-default-last` to what I'm using, hope that's OK. – Matt Dec 16 '22 at 15:57
  • Well, at least on Emacs version 28 and higher, although `read-from-minibuffer` passes only 5 args, the length of the args passed to `eval-expression-advice-default-last` is 7, so that is not the cause of the problem here. I have printed the args that `eval-expression-advice-default-last` returns (when using the `(nth 5 args)`), and all looks fine, the 'default-value' gets passed. However, when pressing enter to select the default value, then it messages `[End of file during parsing]`. So if your code works on Emacs 27, then it is even more likely that this is a bug on Emacs 28 up. – dalanicolai Dec 17 '22 at 04:52
  • Yes it is great that you suggest improvements on the answer. However, I have tested your version on Emacs 27 (where indeed `eval-expression-advice-default-last` receives only 5 args) but anyway, I get the same error as when using my version in Emacs version > 27, namely `[End of file during parsing]`. (Also, you could directly append the default-value to args, instead of extending the list first and then setting it). And of course... nice that you made it working for you! – dalanicolai Dec 17 '22 at 06:21