Hello, I usually run the same command with async-shell-command, however I have to navigate back to the last command history to trigger my last run command, is there any way async-shell-command (and shell-command) can autofill with last run command so that I just hit enter without extra steps?

  • abbreviatedman@fediverser.communick.devB
    link
    fedilink
    English
    arrow-up
    1
    ·
    11 months ago

    Check the documentation of async-shell-command (M-x describe-function async-shell-command RET), and you’ll see that it takes one necessary argument, the command to run.

    If you run it interactively (through its keybinding or M-x), it prompts you for that argument. Type in the command for Emacs, and Emacs will pass that as the argument to async-shell-command.

    But if you want to, you can call it from Lisp and pass in the argument yourself! Try executing it in the scratch buffer or by running eval-expression (I believe M-: by default).

    (async-shell-command "git status")
    

    That will run git status and give you the result

    Now if you want to run that bit of code more often, wrap it in a function and assign it a keybinding!

    (defun crj-check-git-status ()
      (interactive)
      (async-shell-command "git status"))
    
    (general-define-key (kbd "C-M-&") #'crj-check-git-status)
    

    Would anyone else handle this differently? I’m still learning myself!

      • abbreviatedman@fediverser.communick.devB
        link
        fedilink
        English
        arrow-up
        1
        ·
        11 months ago

        I like your thinking!

        Here’s a solution for automatically running the last command:

          (defun repeat-most-recent-shell-command-asynchronously ()
            (interactive)
            (async-shell-command (car shell-command-history)))
        

        It has two drawbacks:

        • shell-command-history doesn’t differentiate between asynchronous and synchronous shell commands, so you may accidentally repeat an async command synchronously (if you differentiate at all)—there are ways to look through the history for the one you want, however
        • you’re repeating automatically, not auto filling and giving yourself the chance to decide you want to run a different command instead

        Honestly probably not worth solving that second issue, as you could bind a function like the one above and then just use that binding instead of the usual when you want to repeat. (Actually, now that I’ve thought this through, I think that’s exactly what I’m gonna do.)

        If you want the auto fill behavior and not the repeat behavior, you could advise the shell-command functions to grab (car shell-command-history) and insert it into the minibuffer.

        Thanks for the thought-provoking question! I think I’m gonna go add this function now. : )

  • abbreviatedman@fediverser.communick.devB
    link
    fedilink
    English
    arrow-up
    1
    ·
    11 months ago

    I tweaked the answer somewhat, as it bothered me that we were getting the most recent command inserted and having it show up again when you decide to go back to an earlier command. In other words, with the OP’s advice, we get the most recent command in the minibuffer history twice, once auto-filled and once in the regular history.

    I’m sure there are cleaner ways to do it than using a custom global variable no other functions know about, which could I think lead to some weird side effects at some point… but for now, I like the behavior:

    (defvar crj-most-recent-shell-command (car shell-command-history))
    
    (defun crj--pop-shell-command-history (fn &rest _args)
      (setq crj-most-recent-shell-command (car shell-command-history))
      (let ((shell-command-history (cdr shell-command-history)))
        (apply fn _args)))
    
    (defun crj--auto-fill-shell-commands (args)
      (list (car args) crj-most-recent-shell-command))
    
    (advice-add 'read-shell-command :around #'crj--pop-shell-command-history)
    (advice-add 'read-shell-command :filter-args #'crj--auto-fill-shell-commands)
    

    Let me know if anyone has improvements/objections!

    • nqminhuit@fediverser.communick.devOPB
      link
      fedilink
      English
      arrow-up
      1
      ·
      11 months ago

      I saw your final version on your config, just a small question, what does '(shell-command-history . 1) means? If I understand it correctly, it returns a new list without the last 1 item from the original list, however I tried to evaluate it on emacs but it does not return the value.

      • abbreviatedman@fediverser.communick.devB
        link
        fedilink
        English
        arrow-up
        1
        ·
        11 months ago

        Yeah, that one took some digging! I found the relevant bit in the Elisp manual. I should have some elisp for you to access the area of the manual within Emacs but I’m not there yet. : ) Here’s the HTML version.

        That bit of a code is a cons pair, a list of the symbol shell-command-history and the number 1. The history argument to completing-read (and therefore by extension read-shell-command) can take in either a symbol for a history variable or a cons pair of a history variable symbol and an index to start with.

  • abbreviatedman@fediverser.communick.devB
    link
    fedilink
    English
    arrow-up
    1
    ·
    11 months ago

    Slight improvement on your advising function, OP: this version starts the shell history you scroll through at index 1, so that after you go up one from your most recent-command, you don’t see the most recent command again.

    (defun crj--auto-fill-shell-commands (args)
      (list (car args) (car shell-command-history) '(shell-command-history . 1) (cdddr args)))
    
    (advice-add 'read-shell-command :filter-args #'crj--auto-fill-shell-commands)
    

    Is there a cleaner way to handle the args than this unpleasing cdddr? Something I can do with &rest?