From 6a74607b85f7d6f278f9044772d02866067dbaea Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Thu, 24 Nov 2022 23:10:38 -0800 Subject: [PATCH] Fix some bugs based in eshell and vterm After making my presentation on eshell, I encountered some bugs that needed addressing. --- ha-eshell.org | 74 ++++++++++++++++++++++++++------------------ ha-remoting.org | 81 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 98 insertions(+), 57 deletions(-) diff --git a/ha-eshell.org b/ha-eshell.org index 077f2d9..8096c4a 100644 --- a/ha-eshell.org +++ b/ha-eshell.org @@ -413,35 +413,35 @@ I’m calling the ability to get a buffer contents, /flow/ (Fetch contents as Li - /as a string/ :: no conversion #+begin_src emacs-lisp - (defun eshell/flow (&rest args) - "Output the contents of one or more buffers as a string. - Usage: flow [OPTION] [BUFFER ...] - -h, --help show this usage screen - -l, --lines output contents as a list of lines - -w, --words output contents as a list of space-separated elements " - (let* ((options (eshell-getopts '((:name words :short "w" :long "words") - (:name lines :short "l" :long "lines") - (:name string :short "s" :long "string") - (:name help :short "h" :long "help" - :help eshell/flow)) - args)) - (buffers (gethash 'parameters options)) - (content (thread-last parameters - (-map 'eshell-flow-buffer-contents) - (s-join "\n")))) - (if (gethash 'help options) - (error (documentation 'eshell/flow)) + (defun eshell/flow (&rest args) + "Output the contents of one or more buffers as a string. + Usage: flow [OPTION] [BUFFER ...] + -h, --help show this usage screen + -l, --lines output contents as a list of lines + -w, --words output contents as a list of space-separated elements " + (let* ((options (eshell-getopts '((:name words :short "w" :long "words") + (:name lines :short "l" :long "lines") + (:name string :short "s" :long "string") + (:name help :short "h" :long "help" + :help eshell/flow)) + args)) + (buffers (gethash 'parameters options)) + (content (thread-last buffers + (-map 'eshell-flow-buffer-contents) + (s-join "\n")))) + (if (gethash 'help options) + (error (documentation 'eshell/flow)) - ;; No buffer specified? Use the default buffer's contents: - (unless buffers - (setq content - (eshell-flow-buffer-contents ha-eshell-ebbflow-buffername))) + ;; No buffer specified? Use the default buffer's contents: + (unless buffers + (setq content + (eshell-flow-buffer-contents ha-eshell-ebbflow-buffername))) - ;; Do we need to convert the output to lines or split on words? - (cond - ((gethash 'words options) (split-string content)) - ((gethash 'lines options) (split-string content "\n")) - (t content))))) + ;; Do we need to convert the output to lines or split on words? + (cond + ((gethash 'words options) (split-string content)) + ((gethash 'lines options) (split-string content "\n")) + (t content))))) #+end_src Straight-forward to acquire the contents of a buffer : @@ -699,6 +699,7 @@ The [[https://github.com/joddie/pcre2el][pcre2el]] project can convert from a Li (regexp "[0-9]\\{1,2\\}"))) (ipaddr (seq b256 "." b256 "." b256 "." b256)) (time (seq digit (optional digit) ":" (= 2 digit) (optional ":" (= 2 digit)))) + (email (seq (1+ (regexp "[^,< ]")) "@" (1+ (seq (1+ (any alnum "-"))) ".") (1+ alnum))) (date (seq (= 2 digit) (or "/" "-") (= 2 digit) (or "/" "-") (= 4 digit))) (ymd (seq (= 4 digit) (or "/" "-") (= 2 digit) (or "/" "-") (= 2 digit))) (uuid (seq (= 8 hex) "-" (= 3 (seq (= 4 hex) "-")) (= 12 hex))) @@ -1028,8 +1029,8 @@ This requires capture templates that don’t do any formatting. I will reused =c (defun ha-eshell-engineering-capture (capture-template comment cmd out) "Capture formatted string in CAPTURE-TEMPLATE. Base the string created on COMMENT, CMD, and OUT. Return OUTPUT." - (let* ((command (s-trim cmd)) - (output (s-trim out)) + (let* ((command (when cmd (s-trim cmd))) + (output (when out (s-trim out))) (results (concat (when comment (format "%s\n\n" comment)) (when command (format "#+begin_src shell\n %s\n#+end_src\n\n" command)) @@ -1061,6 +1062,21 @@ This function simply calls [[help-org-capture][org-capture]] with [[info:org#Tem "Call `org-capture' with the `ee' template to enter text into the engineering notebook." (org-capture nil "ee")) #+end_src + +#+begin_src emacs-lisp + (defun ha-eshell-target-engineering-notebook (output) + "Write OUTPUT into the engineering notebook via `org-capture'." + (ha-eshell-engineering-capture "ef" nil nil output)) + + (defun ha-eshell-target-clocked-in-task (output) + "Write OUTPUT into the current clocked in task via `org-capture'." + (ha-eshell-engineering-capture "cc" nil nil output)) +#+end_src +And finally, add our new functions to [[elisp(describe-variable 'eshell-virtual-targets)][eshell-virtual-targets]]: +#+begin_src emacs-lisp + (add-to-list 'eshell-virtual-targets '("/dev/e" ha-eshell-target-engineering-notebook nil)) + (add-to-list 'eshell-virtual-targets '("/dev/c" ha-eshell-target-engineering-notebook nil)) +#+end_src * 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 diff --git a/ha-remoting.org b/ha-remoting.org index a93017d..e051ba1 100644 --- a/ha-remoting.org +++ b/ha-remoting.org @@ -241,19 +241,16 @@ This is used in calls to =interactive= to select a host." Simply calling =vterm= fails to load my full environment, so this allows me to start the terminal in a particular directory (defaulting to the root of the current project): #+begin_src emacs-lisp -(defun ha-shell (&optional directory) - "Creates and tidies up a =vterm= terminal shell in side window." - (interactive (list (read-directory-name "Starting Directory: " (projectile-project-root)))) - (let* ((win-name "Terminal") - (buf-name (format "*%s*" win-name)) - (default-directory (or directory default-directory))) - (setq ha-latest-ssh-window-name buf-name) - (if (not (fboundp 'vterm)) - (make-term win-name ha-ssh-shell) - (vterm buf-name) - ;; (ha-ssh-send "source ~/.bash_profile" buf-name) - ;; (ha-ssh-send "clear" buf-name) - ))) + (defun ha-shell (&optional directory) + "Creates and tidies up a =vterm= terminal shell in side window." + (interactive (list (read-directory-name "Starting Directory: " (projectile-project-root)))) + (let* ((win-name (ha--terminal-name-from-dir directory)) + (buf-name (format "*%s*" win-name)) + (default-directory (or directory default-directory))) + (setq ha-latest-ssh-window-name buf-name) + (if (not (fboundp 'vterm)) + (make-term win-name ha-ssh-shell) + (vterm buf-name)))) #+end_src Before we leave this section, I realize that I would like a way to /add/ to my list of hosts: @@ -264,26 +261,54 @@ Before we leave this section, I realize that I would like a way to /add/ to my l (add-to-list 'ha-ssh-favorite-hostnames (cons hostname ip-address))) #+end_src ** Programmatic Interface +Let’s send stuff to it: +#+begin_src emacs-lisp + (defun ha-shell-send (command &optional directory) + "Send COMMAND to existing shell based on DIRECTORY. + If the shell doesn't already exist, start on up by calling + the `ha-shell' function." + (let* ((win-name (ha--terminal-name-from-dir directory)) + (win-rx (rx "*" (literal win-name) "*")) + (bufs (seq-filter (lambda (b) (when (string-match win-rx (buffer-name b)) b)) + (buffer-list))) + (buf (first bufs))) + (unless buf + (setq buf (ha-shell directory))) + (ha-ssh-send command buf))) + + (defun ha--terminal-name-from-dir (&optional directory) + "Return an appropriate title for a terminal based on DIRECTORY. + If DIRECTORY is nil, use the `projectile-project-name'." + (unless directory + (setq directory (projectile-project-name))) + (format "Terminal: %s" (file-name-base (directory-file-name directory)))) + + (ert-deftest ha--terminal-name-from-dir-test () + (should + (string= (ha--terminal-name-from-dir "~/other/hamacs/") "Terminal: hamacs")) + (should + (string= (ha--terminal-name-from-dir) "Terminal: hamacs"))) +#+end_src The previous functions (as well as my own end of sprint demonstrations) often need to issue some commands to a running terminal session, which is a simple wrapper around a /send text/ and /send return/ sequence: #+begin_src emacs-lisp -(defun ha-ssh-send (phrase &optional window-name) - "Send command PHRASE to the currently running SSH instance. -If you want to refer to another session, specify the correct WINDOW-NAME. -This is really useful for scripts and demonstrations." - (unless window-name - (setq window-name ha-latest-ssh-window-name)) + (defun ha-ssh-send (phrase &optional window-name) + "Send command PHRASE to the currently running SSH instance. + If you want to refer to another session, specify the correct WINDOW-NAME. + This is really useful for scripts and demonstrations." + (unless window-name + (setq window-name ha-latest-ssh-window-name)) + (save-window-excursion + (pop-to-buffer window-name) - (pop-to-buffer window-name) - - (if (fboundp 'vterm) - (progn - (vterm-send-string phrase) - (vterm-send-return)) - (progn - (term-send-raw-string phrase) - (term-send-input)))) + (if (fboundp 'vterm) + (progn + (vterm-send-string phrase) + (vterm-send-return)) + (progn + (term-send-raw-string phrase) + (term-send-input))))) #+end_src On the rare occasion that I write a shell script, or at least, need to execute some one-line shell commands from some document, I have a function that combines a /read line from buffer/ and then send it to the currently running terminal: