From 2a6302c43d26b7f411d0509249d63ac294f28837 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 23 Sep 2022 16:20:56 -0700 Subject: [PATCH] Add $OUTPUT and $LAST eshell variables These contain the output from the last eshell command. Cool how easily I implemented this. --- ha-eshell.org | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/ha-eshell.org b/ha-eshell.org index 613c926..329ec1b 100644 --- a/ha-eshell.org +++ b/ha-eshell.org @@ -210,6 +210,78 @@ This allows us to replace some of our aliases with functions: (eshell-fn-on-files 'find-file-other-window 'find-file-other-window args)) #+end_src We’ll leave the =e= alias to replace the =eshell= buffer window. +** Last Results +The [[https://github.com/mathiasdahl/shell-underscore][shell-underscore]] project looks pretty cool, where the =_= character represents a /filename/ with the contents of the previous command (you know, like if you were planning on it, you’d =tee= at the end of every command). An interesting idea that I could duplicate. + +While diving into the =eshell= source code, I noticed that the special variable, =$$= can be used /sometimes/ as the output of the last command. For instance: +#+begin_example +$ echo "hello world" +hello world +$ echo $$ +hello world +#+end_example +However, what I would like is something like this to work: +#+begin_example +$ ls *.org(U) +a.org b.org f.org +$ rg "foobar" $$ +#+end_example + +The problem /may/ be between calling Emacs functions versus external commands, as the =echo= works, but the call to =ls= doesn’t: +#+begin_example +$ ls *.org(U) b.txt +a.org b.org f.org b.txt + +$ echo Nam $$ +("Nam" nil) +#+end_example + +However, we could add a hook that runs /after/ every command to copy the output to a variables of our choosing: +#+begin_src emacs-lisp + (defvar OUTPUT "" + "Contains the output from the last eshell command executed.") + + (defvar LAST nil + "Contains a list of elements from the last eshell command executed.") +#+end_src + +Why two variables? Well unlike the behavior of the original shell (and most of its descendents, like =bash=), =eshell= doesn’t automatically split on whitespace. For instance, =echo= called this way: +#+begin_example +$ echo a b *.txt +("a" "b" + ("b.txt" "date today.txt")) +#+end_example +Is given a list of /three elements/: =a=, =b=, and a list of all files in the current directory with an =.org= extension. An interesting side-effect is that spaces in filenames are /often okay/. So we want =$OUTPUT= to contain the command’s output /as a string/, and we have, =$LAST= contains the same stuff, but separated by spaces, into a list. So, if we are passing the output from =ls= to =grep=, we would use =$LAST= to represent files. + +The following function does the work of saving the output of the last command. We can get this because after every command, eshell updates two variables, [[elisp:(describe-variable 'eshell-last-input-end)][eshell-last-input-end]] (the start of the output), and [[elisp:(describe-variable 'eshell-last-output-start)][eshell-last-output-start]] (the end of the output): +#+begin_src emacs-lisp + (defun ha-eshell-store-last-output () + "Store the output from the last eshell command. + Called after every command by connecting to the `eshell-post-command-hook'." + (setq OUTPUT + (s-trim + (buffer-substring-no-properties eshell-last-input-end eshell-last-output-start))) + (setq LAST + (split-string (rx (one-or-more space)) OUTPUT))) +#+end_src + +Now we save this output after every command by adding it to the [[elisp:(describe-variable 'eshell-post-command-hook)][eshell-post-command-hook]]: +#+begin_src emacs-lisp + (add-hook 'eshell-post-command-hook 'ha-eshell-store-last-output) +#+end_src + +Success: +#+begin_example +$ ls *.org(U) b.txt +a.org b.txt + +$ rg Nam $LAST +a.org +8:Nam vestibulum accumsan nisl. + +b.txt +1:Nam euismod tellus id erat. +#+end_example * Special Prompt Following [[http://blog.liangzan.net/blog/2012/12/12/customizing-your-emacs-eshell-prompt/][these instructions]], we build a better prompt with the Git branch in it (Of course, it matches my Bash prompt). First, we need a function that returns a string with the Git branch in it, e.g. ":master" #+begin_src emacs-lisp :tangle no