Compare commits

...

5 commits

Author SHA1 Message Date
Howard Abrams
6155e58879 Markdown snippets 2025-03-10 14:53:50 -07:00
Howard Abrams
2f1a517391 Moving to Emacs 30 and removing ts refs
Now that tree sitter is built in, might as well embrace it.
2025-03-10 14:51:58 -07:00
Howard Abrams
9ab67ce2d1 Convert to use eat instead of vterm 2025-03-10 14:50:49 -07:00
Howard Abrams
ac4d2cbc0b Fix remaining Emacs 30 bugs and issues. 2025-03-06 11:32:03 -08:00
Howard Abrams
6a28307eca Can use telnet or ssh for MUD connections 2025-03-03 13:17:47 -08:00
13 changed files with 481 additions and 377 deletions

View file

@ -43,6 +43,7 @@ Best success comes from using the [[https://github.com/d12frosted/homebrew-emacs
I find that I need to … at least, on my work computer, install two different versions of Emacs that I use to distinguish one for “work” and the other for other activities, like IRC and [[file:ha-feed-reader.org][elfeed]]. To that end, I run the following command to install Emacs: I find that I need to … at least, on my work computer, install two different versions of Emacs that I use to distinguish one for “work” and the other for other activities, like IRC and [[file:ha-feed-reader.org][elfeed]]. To that end, I run the following command to install Emacs:
#+begin_src sh #+begin_src sh
brew reinstall $(brew deps emacs-plus@30)
brew install emacs-plus@30 --with-native-comp --with-mailutils --with-imagemagick --with-savchenkovaleriy-big-sur-icon --with-no-frame-refocus --debug brew install emacs-plus@30 --with-native-comp --with-mailutils --with-imagemagick --with-savchenkovaleriy-big-sur-icon --with-no-frame-refocus --debug
#+end_src #+end_src
And if it fails, choose =shell= and type: And if it fails, choose =shell= and type:

View file

@ -19,7 +19,7 @@
"A WAV or AU file used at the completion of a function.") "A WAV or AU file used at the completion of a function.")
;; My replacement in case we can't play internal sounds: ;; My replacement in case we can't play internal sounds:
(defun beep--beep () (defun beep-beep ()
"Play a default notification sound file. "Play a default notification sound file.
Customize the variable, `beep-alert-sound-file' to adjust the sound." Customize the variable, `beep-alert-sound-file' to adjust the sound."
(if (fboundp 'play-sound-internal) (if (fboundp 'play-sound-internal)
@ -29,12 +29,12 @@ Customize the variable, `beep-alert-sound-file' to adjust the sound."
(defvar beep-speech-executable "say %s" (defvar beep-speech-executable "say %s"
"An OS-dependent shell string to speak. Replaces `%s' with a phrase.") "An OS-dependent shell string to speak. Replaces `%s' with a phrase.")
(defun beep--speak (phrase) (defun beep-speak (phrase)
"Call a program to speak the string, PHRASE. "Call a program to speak the string, PHRASE.
Customize the variable, `beep-speech-executable'." Customize the variable, `beep-speech-executable'."
(let ((command (format beep-speech-executable phrase))) (let ((command (format beep-speech-executable phrase)))
(save-window-excursion (ignore-errors
(async-shell-command command)))) (call-process-shell-command command))))
(defun beep-when-finished (phrase &optional to-speak) (defun beep-when-finished (phrase &optional to-speak)
"Notify us with string, PHRASE, to grab our attention. "Notify us with string, PHRASE, to grab our attention.
@ -42,8 +42,8 @@ Useful after a long process has completed, but use sparingly,
as this can be pretty distracting." as this can be pretty distracting."
(when (functionp 'alert) (when (functionp 'alert)
(alert phrase :title "Completed")) (alert phrase :title "Completed"))
(beep--beep) (beep-beep)
(beep--speak (or to-speak phrase)) (beep-speak (or to-speak phrase))
(message phrase)) (message phrase))
(defun compile-and-notify () (defun compile-and-notify ()
@ -57,31 +57,12 @@ See `beep-when-finished' for details."
(defvar beep-func-too-long-time 5 (defvar beep-func-too-long-time 5
"The number of seconds a function runs before it is considered taking too much time, and needing to be alerted when it has finished.") "The number of seconds a function runs before it is considered taking too much time, and needing to be alerted when it has finished.")
(defun beep--after-function (func)
"Call the function, FUNC, interactively, and notify us when completed."
(let ((start-time (current-time))
duration)
(call-interactively func)
(setq duration (thread-first
(current-time)
(time-subtract start-time)
decode-time
first))
(when (> duration beep-func-too-long-time)
(beep-when-finished (format "The function, %s, has finished." func)))))
(defun recompile-and-notify ()
"Call `recompile' and notify us when finished.
See `beep-when-finished' for details."
(interactive)
(beep--after-function 'recompile))
(global-set-key (kbd "C-c c") 'recompile-and-notify) (global-set-key (kbd "C-c c") 'recompile-and-notify)
(global-set-key (kbd "C-c C") 'compile-and-notify) (global-set-key (kbd "C-c C") 'compile-and-notify)
(defun beep-when-runs-too-long (orig-function &rest args) (defun beep-when-runs-too-long (orig-function &rest args)
"Notifies us about the completion of ORIG-FUNCTION. "Notifies us about the completion of ORIG-FUNCTION.
Useful as after advice to long-running functions, for instance: Useful as after advice to long-running functions, for instance:
(advice-add 'org-publish :around #'beep-when-runs-too-long)" (advice-add 'org-publish :around #'beep-when-runs-too-long)"
(let ((start-time (current-time)) (let ((start-time (current-time))

View file

@ -29,6 +29,10 @@ A literate programming file for configuring Emacs.
;; loading sequence. ;; loading sequence.
;; ;;
;;; Code: ;;; Code:
;; Used functions defined elsewhere:
(defun font-icons (collection label &rest args)
(or (plist-get args :title) label))
#+end_src #+end_src
* Basic Configuration * Basic Configuration
I begin configuration of Emacs that isnt /package-specific/. I begin configuration of Emacs that isnt /package-specific/.
@ -287,6 +291,36 @@ This function can be called interactively with a URL and a directory (and it att
#+end_src #+end_src
** Completing Read User Interface ** Completing Read User Interface
After using Ivy, I am going the route of a =completing-read= interface that extends the original Emacs API, as opposed to implementing backend-engines or complete replacements. After using Ivy, I am going the route of a =completing-read= interface that extends the original Emacs API, as opposed to implementing backend-engines or complete replacements.
One enhancement to =completing-read= is to allow either a property list or an associate list for choices, and then return the /value/.
#+BEGIN_SRC emacs-lisp
(defun completing-read-alist (prompt collection
&optional predicate require-match
initial-input hist def inherit-input-method)
"List `completing-read', but COLLECTION is an alist, and it returns value.
The is, the _associative bit_.
PROMPT is a string to prompt with; normally it ends in a colon and a space.
PREDICATE, REQUIRE-MATCH, HIST and INHERIT-INPUT-METHOD is the same.
DEF is the default return without a match."
(let ((x (completing-read prompt collection predicate require-match
initial-input hist def inherit-input-method)))
(alist-get x collection x nil 'equal)))
#+END_SRC
This means (and I use this fairly often), that the /key/ is shows as a choice, the function returns the /value/.
#+BEGIN_SRC emacs-lisp :tangle no
(completing-read-alist "Choose a language: "
'(("Emacs Lisp" . "elisp.org")
("Python" . "python.org")
("Visual Basic" . "visual-basic.org")
;; ...
))
#+END_SRC
*** Vertico *** Vertico
The [[https://github.com/minad/vertico][vertico]] package puts the completing read in a vertical format, and like [[https://github.com/raxod502/selectrum#vertico][Selectrum]], it extends Emacs built-in functionality, instead of adding a new process. This means all these projects work together. The [[https://github.com/minad/vertico][vertico]] package puts the completing read in a vertical format, and like [[https://github.com/raxod502/selectrum#vertico][Selectrum]], it extends Emacs built-in functionality, instead of adding a new process. This means all these projects work together.
#+begin_src emacs-lisp #+begin_src emacs-lisp
@ -779,43 +813,43 @@ Magnar Sveen's [[https://github.com/magnars/expand-region.el][expand-region]] pr
The built-in =isearch= is fantastically simple and useful, bound to ~C-s~, but why not bind searching for the current symbol? The built-in =isearch= is fantastically simple and useful, bound to ~C-s~, but why not bind searching for the current symbol?
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(global-set-key (kbd "m-s m-s") 'isearch-forward-thing-at-point) (global-set-key (kbd "M-s M-s") 'isearch-forward-thing-at-point)
#+END_SRC #+END_SRC
I like Charles Chois [[https://github.com/kickingvegas/casual][Casual Suite]], and his original [[https://github.com/kickingvegas/cc-isearch-menu][cc-isearch-menu]] was great at seeing the /buried/ features. Ive duplicated the features using [[https://github.com/jerrypnz/major-mode-hydra.el][pretty-hydra]]. In the middle of a search, type ~⌘-s~ (Command-s), and menu of options I dont remember appear. I like Charles Chois [[https://github.com/kickingvegas/casual][Casual Suite]], and his original [[https://github.com/kickingvegas/cc-isearch-menu][cc-isearch-menu]] was great at seeing the /buried/ features. Ive duplicated the features using [[https://github.com/jerrypnz/major-mode-hydra.el][pretty-hydra]]. In the middle of a search, type ~⌘-s~ (Command-s), and menu of options I dont remember appear.
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp :tangle no
(defvar ha-isearch--title (font-icons 'faicon "magnifying_glass" (defvar ha-isearch--title (font-icons 'faicon "magnifying_glass"
:title "Search Options")) :title "Search Options"))
(pretty-hydra-define isearch-mode (pretty-hydra-define isearch-mode
(:color amaranth :quit-key "C-s" :title ha-isearch--title) (:color amaranth :quit-key "C-s" :title ha-isearch--title)
("Movement" ("Movement"
(("n" isearch-repeat-forward "Forward") (("n" isearch-repeat-forward "Forward")
("p" isearch-repeat-backward "Backward") ("p" isearch-repeat-backward "Backward")
("j" avy-isearch "Jump" :color blue)) ("j" avy-isearch "Jump" :color blue))
"Expand" "Expand"
((">" isearch-yank-symbol-or-char "Full symbol") ((">" isearch-yank-symbol-or-char "Full symbol")
("z" isearch-yank-until-char "Until char") ("z" isearch-yank-until-char "Until char")
("$" isearch-yank-line "Full line")) ("$" isearch-yank-line "Full line"))
"Replace" "Replace"
(("R" isearch-query-replace "Literal") (("R" isearch-query-replace "Literal")
("Q" isearch-query-replace-regexp "Regexp") ("Q" isearch-query-replace-regexp "Regexp")
("<return>" isearch-exit "Stay" :color blue)) ("<return>" isearch-exit "Stay" :color blue))
"Toggles" "Toggles"
(("w" isearch-toggle-word "Word only" :toggle t) (("w" isearch-toggle-word "Word only" :toggle t)
("s" isearch-toggle-symbol "Full symbol" :toggle t) ("s" isearch-toggle-symbol "Full symbol" :toggle t)
("r" isearch-toggle-regexp "Regexp" :toggle t) ("r" isearch-toggle-regexp "Regexp" :toggle t)
("c" isearch-toggle-case-fold "Case Sensitive" :toggle t)) ("c" isearch-toggle-case-fold "Case Sensitive" :toggle t))
"Highlight" "Highlight"
(("H" isearch-highlight-regexp "Matches" :color blue) (("H" isearch-highlight-regexp "Matches" :color blue)
("L" isearch-highlight-lines-matching-regexp "Lines" :color blue)) ("L" isearch-highlight-lines-matching-regexp "Lines" :color blue))
"Other" "Other"
(("e" isearch-edit-string "Edit") (("e" isearch-edit-string "Edit")
("o" isearch-occur "Occur" :color blue) ("o" isearch-occur "Occur" :color blue)
("C-g" isearch-abort "Abort" :color blue)))) ("C-g" isearch-abort "Abort" :color blue))))
(define-key isearch-mode-map (kbd "s-s") 'isearch-mode/body) (define-key isearch-mode-map (kbd "s-s") 'isearch-mode/body)
#+end_src #+end_src
Pressing ~C-g~ aborts, and ~Return~ exits leaving the point in place. Typing ~C-s~ in the menu stops the menu to continue searching. Pressing ~C-g~ aborts, and ~Return~ exits leaving the point in place. Typing ~C-s~ in the menu stops the menu to continue searching.

View file

@ -381,8 +381,7 @@ This project replaces [[https://github.com/domtronn/all-the-icons.el][all-the-ic
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package nerd-icons (use-package nerd-icons
:straight (nerd-icons :type git :host github :repo "rainstormstudio/nerd-icons.el" :straight (nerd-icons :type git :host github :repo "rainstormstudio/nerd-icons.el")
:files (:defaults "data"))
:custom :custom
;; The Nerd Font you want to use in GUI defaults to fixed-font: ;; The Nerd Font you want to use in GUI defaults to fixed-font:
(nerd-icons-font-family ha-fixed-font)) (nerd-icons-font-family ha-fixed-font))
@ -392,39 +391,39 @@ The way we access the /font icons/ has always been … odd; needing to specify t
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defun font-icons (collection label &rest args) (defun font-icons (collection label &rest args)
"Abstraction over `nerd-icons' project. "Abstraction over `nerd-icons' project.
LABEL is a short icon description merged with COLLECTION to LABEL is a short icon description merged with COLLECTION to identify an
identify an icon to use. For instance, 'faicon or 'octicon. icon to use. For instance, 'faicon or 'octicon.
ARGS, a plist, contain the title, sizing and other information. ARGS, a plist, contain the title, sizing and other information.
For instance: For instance:
(font-icons 'faicon \"file\" :title \"File Management\") (font-icons 'faicon \"file\" :title \"File Management\")
The goal is to take: The goal is to take:
(all-the-icons-octicon \"git-branch\") (all-the-icons-octicon \"git-branch\")
And reformat to: And reformat to:
(font-icons 'octicon \"git-branch\")" (font-icons 'octicon \"git-branch\")"
(let* ((func (intern (format "nerd-icons-%s" collection))) (let* ((func (intern (format "nerd-icons-%s" collection)))
(short (cl-case collection (short (cl-case collection
('octicon "oct") ('octicon "oct")
('faicon "fa") ('faicon "fa")
('mdicon "md") ('mdicon "md")
('codicon "cod") ('codicon "cod")
('sucicon "custom") ('sucicon "custom")
('devicon "dev") ('devicon "dev")
(t collection))) (t collection)))
(title (plist-get args :title)) (title (plist-get args :title))
(space (plist-get args :space)) (space (plist-get args :space))
(icon (format "nf-%s-%s" short (icon (format "nf-%s-%s" short
(string-replace "-" "_" label)))) (string-replace "-" "_" label))))
;; With the appropriate nerd-icons function name, ;; With the appropriate nerd-icons function name,
;; an expanded icon name, we get the icon string: ;; an expanded icon name, we get the icon string:
(concat (apply func (cons icon args)) (concat (apply func (cons icon args))
(cond (cond
((and title space) (concat (s-repeat space " ") title)) ((and title space) (concat (s-repeat space " ") title))
(title (concat " " title)))))) (title (concat " " title))))))
#+END_SRC #+END_SRC
This replaces the /title generator/ for [[file:ha-config.org::*Leader Sequences][major-mode-hydra]] project to include a nice looking icon: This replaces the /title generator/ for [[file:ha-config.org::*Leader Sequences][major-mode-hydra]] project to include a nice looking icon:

View file

@ -64,7 +64,6 @@ The [[https://github.com/antonj/Highlight-Indentation-for-Emacs][Highlight-Inden
(use-package highlight-indentation (use-package highlight-indentation
:straight (:host github :repo "antonj/Highlight-Indentation-for-Emacs") :straight (:host github :repo "antonj/Highlight-Indentation-for-Emacs")
:hook ((yaml-mode . highlight-indentation-mode) :hook ((yaml-mode . highlight-indentation-mode)
(yaml-ts-mode . highlight-indentation-mode)
(python-mode . highlight-indentation-mode))) (python-mode . highlight-indentation-mode)))
#+end_src #+end_src
@ -102,41 +101,39 @@ Doing a lot of [[https://github.com/yoshiki/yaml-mode][YAML work]], but the =ya
, so Ive switch to [[https://github.com/zkry/yaml-pro][yaml-pro]] that is now based on Tree Sitter. Lets make sure the Tree-Sitter version works: , so Ive switch to [[https://github.com/zkry/yaml-pro][yaml-pro]] that is now based on Tree Sitter. Lets make sure the Tree-Sitter version works:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(when (treesit-available-p) (use-package yaml-mode
(use-package yaml-ts-mode :after mixed-pitch
:straight (:type built-in) :mode ((rx ".yamllint")
:after mixed-pitch (rx ".y" (optional "a") "ml" string-end))
:mode ((rx ".yamllint") :hook (yaml-mode . (lambda () (mixed-pitch-mode -1)))
(rx ".y" (optional "a") "ml" string-end)) :mode-hydra
:hook (yaml-ts-mode . (lambda () (mixed-pitch-mode -1))) ((:foreign-keys run)
:mode-hydra ("Simple"
((:foreign-keys run) (("l" ha-yaml-next-section "Next section")
("Simple" ("h" ha-yaml-prev-section "Previous")))))
(("l" ha-yaml-next-section "Next section")
("h" ha-yaml-prev-section "Previous"))))))
#+end_src #+end_src
Allow this mode in Org blocks: Allow this mode in Org blocks:
#+begin_src emacs-lisp :results silent #+begin_src emacs-lisp :results silent
(add-to-list 'org-babel-load-languages '(yaml-ts . t)) (add-to-list 'org-babel-load-languages '(yaml . t))
#+end_src #+end_src
And we hook And we hook
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package yaml-pro (use-package yaml-pro
:straight (:host github :repo "zkry/yaml-pro") :straight (:host github :repo "zkry/yaml-pro")
:after yaml-ts-mode :after yaml-mode
:hook ((yaml-ts-mode . yaml-pro-ts-mode) :hook ((yaml-mode . yaml-pro-mode)))
(yaml-mode . yaml-pro-mode)))
#+end_src #+end_src
Since I can never remember too many keybindings for particular nodes, we create a Hydra just for it. Since I can never remember too many keybindings for particular nodes, we create a Hydra just for it.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defvar ha-yaml--title (font-icons 'devicon "yaml" :title "YAML Mode"))
(use-package major-mode-hydra (use-package major-mode-hydra
:after yaml-pro :after yaml-pro
:config :config
(major-mode-hydra-define yaml-ts-mode (:foreign-keys run) (major-mode-hydra-define yaml-mode (:title ha-yaml--title :foreign-keys run)
("Navigation" ("Navigation"
(("u" yaml-pro-ts-up-level "Up level" :color pink) ; C-c C-u (("u" yaml-pro-ts-up-level "Up level" :color pink) ; C-c C-u
("J" yaml-pro-ts-next-subtree "Next subtree" :color pink) ; C-c C-n ("J" yaml-pro-ts-next-subtree "Next subtree" :color pink) ; C-c C-n
@ -171,10 +168,12 @@ Jinja is a /template/ system that integrates /inside/ formats like JSON, HTML or
The [[https://polymode.github.io/][polymode]] project /glues/ modes like [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]] to [[https://github.com/yoshiki/yaml-mode][yaml-mode]]. The [[https://polymode.github.io/][polymode]] project /glues/ modes like [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]] to [[https://github.com/yoshiki/yaml-mode][yaml-mode]].
I adapted this code from the [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project: I adapted this code from the [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project:
#+begin_src emacs-lisp
#+begin_src emacs-lisp :tangle no
(use-package polymode (use-package polymode
:after yaml-mode jinja2-mode
:config :config
(define-hostmode poly-yaml-hostmode :mode 'yaml-ts-mode) (define-hostmode poly-yaml-hostmode :mode 'yaml-mode)
(defcustom pm-inner/jinja2 (defcustom pm-inner/jinja2
(pm-inner-chunkmode :mode #'jinja2-mode (pm-inner-chunkmode :mode #'jinja2-mode
@ -195,23 +194,23 @@ I adapted this code from the [[https://github.com/emacsmirror/poly-ansible][poly
:hostmode 'poly-yaml-hostmode :hostmode 'poly-yaml-hostmode
:innermodes '(pm-inner/jinja2)) :innermodes '(pm-inner/jinja2))
(major-mode-hydra-define+ yaml-ts-mode nil (major-mode-hydra-define+ yaml-mode nil
("Extensions" (("j" poly-yaml-jinja2-mode "Jinja2"))))) ("Extensions" (("j" poly-yaml-jinja2-mode "Jinja2")))))
#+end_src #+end_src
We need to make sure the =mixed-pitch-mode= doesnt screw things up. We need to make sure the =mixed-pitch-mode= doesnt screw things up.
#+begin_src emacs-lisp #+begin_src emacs-lisp :tangle no
(add-hook 'poly-yaml-jinja2-mode-hook (lambda () (mixed-pitch-mode -1))) (add-hook 'poly-yaml-jinja2-mode-hook (lambda () (mixed-pitch-mode -1)))
#+end_src #+end_src
We /can/ hook this up to Org, via: We /can/ hook this up to Org, via:
#+begin_src emacs-lisp #+begin_src emacs-lisp :tangle no
(add-to-list 'org-babel-load-languages '(poly-yaml-jinja2 . t)) (add-to-list 'org-babel-load-languages '(poly-yaml-jinja2 . t))
#+end_src #+end_src
Now we can use either =yaml-ts= or =poly-yaml-jinja2= (which perhaps we should make an alias?): Now we can use either =yaml= or =poly-yaml-jinja2= (which perhaps we should make an alias?):
#+begin_src poly-yaml-jinja2 :tangle no #+begin_src poly-yaml-jinja2 :tangle no
--- ---
@ -230,15 +229,16 @@ Now we can use either =yaml-ts= or =poly-yaml-jinja2= (which perhaps we should m
#+end_src #+end_src
* Ansible * Ansible
Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/emacs-ansible][ansible-mode]]? Maybe we just have a toggle for when we want the Ansible feature. Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/emacs-ansible][ansible-mode]]? Maybe we just have a toggle for when we want the Ansible feature.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package ansible (use-package ansible
:straight (:host github :repo "k1LoW/emacs-ansible") :straight (:host gitlab :repo "emacs-ansible/emacs-ansible")
;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode) ;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
:config :config
(setq ansible-vault-password-file "~/.ansible-vault-passfile") (setq ansible-vault-password-file "~/.ansible-vault-passfile")
(major-mode-hydra-define+ yaml-ts-mode nil (major-mode-hydra-define+ yaml-mode nil
("Extensions" (("a" ansible "Ansible")))) ("Extensions" (("a" ansible-mode "Ansible"))))
(ha-leader "t y" 'ansible)) (ha-leader "t y" 'ansible-mode))
#+end_src #+end_src
The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable needs to change /per project/, so lets use the =.dir-locals.el= file, for instance: The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable needs to change /per project/, so lets use the =.dir-locals.el= file, for instance:
@ -246,15 +246,23 @@ The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable n
((nil . ((ansible-vault-password-file . "playbooks/.vault-password")))) ((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
#+end_src #+end_src
Since most Ansible files are a combination of YAML and Jinja, the [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project addresses this similar to =web-mode=:
#+BEGIN_SRC emacs-lisp
(use-package poly-ansible
:straight (:host github :repo "emacsmirror/poly-ansible")
:after ansible)
#+END_SRC
The YAML files get access Ansibles documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project (that accesses the [[https://docs.ansible.com/ansible/latest/cli/ansible-doc.html][ansible-doc interface]]): The YAML files get access Ansibles documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project (that accesses the [[https://docs.ansible.com/ansible/latest/cli/ansible-doc.html][ansible-doc interface]]):
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package ansible-doc (use-package ansible-doc
:after yaml-ts-mode :after yaml-mode
:hook (yaml-ts-mode . ansible-doc-mode) :hook (yaml-mode . ansible-doc-mode)
:config :config
;; (add-to-list 'exec-path (expand-file-name "~/.local/share/mise/installs/python/3.10/bin/ansible-doc")) ;; (add-to-list 'exec-path (expand-file-name "~/.local/share/mise/installs/python/3.10/bin/ansible-doc"))
(major-mode-hydra-define+ yaml-ts-mode nil (major-mode-hydra-define+ yaml-mode nil
("Documentation" ("Documentation"
(("D" ansible-doc "Ansible"))))) (("D" ansible-doc "Ansible")))))
#+end_src #+end_src

View file

@ -322,6 +322,7 @@ Install the binary for the [[https://tree-sitter.github.io/][tree-sitter project
#+begin_src sh #+begin_src sh
brew install tree-sitter npm # Since most support packages need that too. brew install tree-sitter npm # Since most support packages need that too.
#+end_src #+end_src
The tree-sitter project does not install any language grammars by default—after all, it would have no idea which particular languages to parse and analyze! The tree-sitter project does not install any language grammars by default—after all, it would have no idea which particular languages to parse and analyze!
Next, using the =tree-sitter= command line tool, create the [[/Users/howard.abrams/Library/Application Support/tree-sitter/config.json][config.json]] file: Next, using the =tree-sitter= command line tool, create the [[/Users/howard.abrams/Library/Application Support/tree-sitter/config.json][config.json]] file:
@ -342,14 +343,17 @@ Normally, you would need to add all the projects to directory clones in =~/src=
git pull origin git pull origin
npm install npm install
done <<EOL done <<EOL
https://github.com/tree-sitter/tree-sitter-css https://github.com/tree-sitter/tree-sitter-go
https://github.com/tree-sitter/tree-sitter-javascript
https://github.com/tree-sitter/tree-sitter-templ
https://github.com/ikatyang/tree-sitter-yaml
https://github.com/tree-sitter/tree-sitter-json https://github.com/tree-sitter/tree-sitter-json
https://github.com/tree-sitter/tree-sitter-css
https://github.com/tree-sitter/tree-sitter-python https://github.com/tree-sitter/tree-sitter-python
https://github.com/tree-sitter/tree-sitter-bash https://github.com/tree-sitter/tree-sitter-bash
https://github.com/tree-sitter/tree-sitter-ruby https://github.com/tree-sitter/tree-sitter-ruby
https://github.com/camdencheek/tree-sitter-dockerfile https://github.com/camdencheek/tree-sitter-dockerfile
https://github.com/alemuller/tree-sitter-make https://github.com/alemuller/tree-sitter-make
https://github.com/ikatyang/tree-sitter-yaml
https://github.com/Wilfred/tree-sitter-elisp https://github.com/Wilfred/tree-sitter-elisp
EOL EOL
#+end_src #+end_src
@ -378,15 +382,18 @@ In most cases,the =npm install= /usually/ works, but I may work on some sort of
NAME=$(pwd | sed 's/.*-//') NAME=$(pwd | sed 's/.*-//')
git pull origin git pull origin
npm install || cargo build || make install # Various build processes!? npm install || make install # Various build processes!?
echo "Do we need to copy the library into ~/.emacs.d/tree-sitter/$NAME ?" echo "Do we need to copy the library into ~/.emacs.d/tree-sitter/$NAME ?"
# if [ "$(uname -o)" = "Darwin" ] if [[ -e libtree-sitter-$NAME.dylib ]]
# then then
# cp libtree-sitter-$NAME.dylib ~/.emacs.d/tree-sitter cp libtree-sitter-$NAME.dylib ~/.emacs.d/tree-sitter
# else fi
# cp libtree-sitter-$NAME.so ~/.emacs.d/tree-sitter
# fi if [[ -e libtree-sitter-$NAME.so ]]
then
cp libtree-sitter-$NAME.so ~/.emacs.d/tree-sitter
fi
done done
#+end_src #+end_src
At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>= At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>=
@ -412,6 +419,7 @@ However, Emacs already has the ability to download and install grammars, so foll
;; (elixir "https://github.com/elixir-lang/tree-sitter-elixir" "main" "src") ;; (elixir "https://github.com/elixir-lang/tree-sitter-elixir" "main" "src")
;; (erlang "https://github.com/WhatsApp/tree-sitter-erlang" "main" "src") ;; (erlang "https://github.com/WhatsApp/tree-sitter-erlang" "main" "src")
(go "https://github.com/tree-sitter/tree-sitter-go") (go "https://github.com/tree-sitter/tree-sitter-go")
(templ "https://github.com/vrischmann/tree-sitter-templ")
;; (haskell "https://github.com/tree-sitter/tree-sitter-haskell" "master" "src") ;; (haskell "https://github.com/tree-sitter/tree-sitter-haskell" "master" "src")
(html "https://github.com/tree-sitter/tree-sitter-html") (html "https://github.com/tree-sitter/tree-sitter-html")
;; (java "https://github.com/tree-sitter/tree-sitter-java" "master" "src") ;; (java "https://github.com/tree-sitter/tree-sitter-java" "master" "src")

View file

@ -61,6 +61,7 @@ Will Schenk has [[https://willschenk.com/articles/2020/tramp_tricks/][a simple e
(setq ad-return-value dockernames)) (setq ad-return-value dockernames))
ad-do-it))) ad-do-it)))
#+end_src #+end_src
Keep in mind you need to /name/ your Docker session, with the =—name= option. I actually do more docker work on remote systems (as Docker seems to make my fans levitate my laptop over the desk). Granted, the =URL= is a bit lengthy, for instance: Keep in mind you need to /name/ your Docker session, with the =—name= option. I actually do more docker work on remote systems (as Docker seems to make my fans levitate my laptop over the desk). Granted, the =URL= is a bit lengthy, for instance:
#+begin_example #+begin_example
/ssh:kolla-compute1.cedev13.d501.eng.pdx.wd|sudo:kolla-compute1.cedev13.d501.eng.pdx.wd|docker:kolla_toolbox:/ /ssh:kolla-compute1.cedev13.d501.eng.pdx.wd|sudo:kolla-compute1.cedev13.d501.eng.pdx.wd|docker:kolla_toolbox:/
@ -77,14 +78,19 @@ Which means, I need to put it as a link in an org file.
* Remote Terminals * Remote Terminals
Sure =iTerm= is nice for connecting and running commands on remote systems, however, it lacks a command line option that allows you to select and manipulate the displayed text without a mouse. This is where Emacs can shine. Sure =iTerm= is nice for connecting and running commands on remote systems, however, it lacks a command line option that allows you to select and manipulate the displayed text without a mouse. This is where Emacs can shine.
Interactive Functions:
- ha-shell :: create a local shell in default-directory. This is an abstraction mostly used for my demonstrations, otherwise, I can just call the =make-term= or =eat= directly.
- ha-ssh :: create a shell on remote system
*Feature One:* *Feature One:*
When calling the =ha-ssh= function, it opens a =vterm= window which, unlike other terminal emulators in Emacs, merges both Emacs and Terminal behaviors. Essentially, it just works. It =vterm= isn't installed, it falls back to =term=. When calling the =ha-ssh= function, it opens a =vterm= window which, unlike other terminal emulators in Emacs, merges both Emacs and Terminal behaviors. Essentially, it just works. It =vterm= isn't installed, it falls back to either =eat= or good ol =term=.
Preload a list of favorite/special hostnames with multiple calls to: Preload a list of favorite/special hostnames with multiple calls to:
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp :tangle no
(ha-ssh-add-favorite-host "Devbox 42" "10.0.1.42") (ha-ssh-add-favorite-host "Devbox 42" "10.0.1.42")
#+end_src #+end_src
Then calling =ha-ssh= function, a list of hostnames is available to quickly jump on a system (with the possibility of fuzzy matching if you have Helm or Ivy installed). Then calling =ha-ssh= function, a list of hostnames is available to quickly jump on a system (with the possibility of fuzzy matching if you have Helm or Ivy installed).
@ -100,25 +106,27 @@ Use the /favorite host/ list to quickly edit a file on a remote system using Tra
Working with remote shell connections programmatically, for instance: Working with remote shell connections programmatically, for instance:
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp :tangle no
(let ((win-name "some-host")) (let ((win-name "some-host"))
(ha-ssh "some-host.in.some.place" win-name) (ha-ssh "some-host.in.some.place" win-name)
(ha-ssh-send "source ~/.bash_profile" win-name) (ha-ssh-send "source ~/.bash_profile" win-name)
(ha-ssh-send "clear" win-name)) (ha-ssh-send "clear" win-name))
;; ... ;; ...
(ha-ssh-exit win-name) (ha-ssh-exit win-name)
#+end_src #+end_src
Actually the =win-name= in this case is optional, as it will use a good default. Actually the =win-name= in this case is optional, as it will use a good default.
** VTerm ** VTerm
I'm not giving up on Eshell, but I am playing around with [[https://github.com/akermu/emacs-libvterm][vterm]], and it is pretty good, but I use it primarily as a more reliable approach for remote terminal sessions. I'm not giving up on Eshell, but I am playing around with [[https://github.com/akermu/emacs-libvterm][vterm]], and it is pretty good, but I use it primarily as a more reliable approach for remote terminal sessions.
VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previous word, and yeah, I want to make sure that both keystrokes do the same thing. VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previous word, and yeah, I want to make sure that both keystrokes do the same thing.
#+begin_src emacs-lisp #+begin_src emacs-lisp :tangle no
(use-package vterm (use-package vterm
:config :config
(ha-leader
"p t" '("terminal" . (lambda () (interactive) (ha-shell (project-root (project-current))))))
(dolist (k '("<C-backspace>" "<M-backspace>")) (dolist (k '("<C-backspace>" "<M-backspace>"))
(define-key vterm-mode-map (kbd k) (define-key vterm-mode-map (kbd k)
(lambda () (interactive) (vterm-send-key (kbd "C-w"))))) (lambda () (interactive) (vterm-send-key (kbd "C-w")))))
@ -143,6 +151,50 @@ VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previou
#+end_src #+end_src
The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor. To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~. The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor. To exit the copy-mode (and copy the selected text to the clipboard), hit ~Return~.
** Eat
While not as fast as [[https://github.com/akermu/emacs-libvterm][vterm]], the [[https://codeberg.org/akib/emacs-eat][Emulate a Terminal]] project (eat) is fast enough, and doesnt require a dedicate library that requires re-compilation. While offering [[https://elpa.nongnu.org/nongnu-devel/doc/eat.html][online documentation]], Im glad for an [[info:eat#Top][Info version]].
#+BEGIN_SRC emacs-lisp
(use-package eat
:straight (:host codeberg :repo "akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi"
"*.ti" ("terminfo/e" "terminfo/efo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el")))
:bind (:map eat-semi-char-mode-map
("C-c C-t" . ha-eat-narrow-to-shell-prompt-dwim))
:config
(defun ha-eat-narrow-to-shell-prompt-dwim ()
(interactive)
(if (buffer-narrowed-p) (widen) (eat-narrow-to-shell-prompt)))
(ha-leader
"p t" '("terminal" . eat-project)))
#+END_SRC
The largest change, is like the venerable [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Term-Mode.html][term mode]], we have different modes:
- =semi-char= :: This DWIM mode works halfway between an Emacs buffer and a terminal. Use ~C-c C-e~ to go to =emacs= mode.
- =emacs= :: Good ol Emacs buffer, use ~C-c C-j~ to go back to =semi-char= mode.
- =char= :: Full terminal mode, use ~M-RET~ to pop back to =semi-char= mode.
- =line= :: Line-oriented mode, not sure why Id use it.
Cool stuff:
- ~C-n~ / ~C-p~ :: scrolls the command history
- ~C-c C-n~ / ~C-c C-p~ :: jumps to the various prompts
What about Evil mode?
TODO: Like =eshell=, the Bash in an EAT terminal has a command =_eat_msg= that takes a handler, and a /message/. Then set up an alist of =eat-message-handler-alist= to decide what to do with it.
TODO: Need to /subtlize/ the =eat-term-color-bright-green= and other settings as it is way too garish.
Make sure you add the following for Bash:
#+BEGIN_SRC bash :tangle no
[ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
source "$EAT_SHELL_INTEGRATION_DIR/bash"
#+END_SRC
** Variables ** Variables
Let's begin by defining some variables used for communication between the functions. Let's begin by defining some variables used for communication between the functions.
@ -159,187 +211,161 @@ Let's begin by defining some variables used for communication between the functi
#+end_src #+end_src
Also, let's make it easy for me to change my default shell: Also, let's make it easy for me to change my default shell:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defvar ha-ssh-shell (shell-command-to-string "type -p fish") (defvar ha-shell "bash" ;; Eat works better with Bash/Zsh
;; (string-trim (shell-command-to-string "type -p fish"))
"The executable to the shell I want to use locally.") "The executable to the shell I want to use locally.")
#+end_src #+end_src
** Terminal Abstractions
Could I abstract the different ways I start terminals in Emacs? The =ha-ssh-term= starts either a [[VTerm]]
or [[Eat]] terminals, depending on what is available. This replaces (wraps) the default [[help:make-term][make-term]].
#+BEGIN_SRC emacs-lisp
(defun ha-make-term (name &optional program startfile &rest switches)
"Create a terminal buffer NAME based on available emulation.
The PROGRAM, if non-nil, is executed, otherwise, this is `ha-shell'.
STARTFILE is the initial text given to the PROGRAM, and the
SWITCHES are the command line options."
(unless program (setq program ha-shell))
(cond
((fboundp 'vterm) (progn (vterm name)
(vterm-send-string (append program switches))
(vterm-send-return)))
((fboundp 'eat) (progn (switch-to-buffer
(apply 'eat-make (append (list name program startfile)
switches)))
(setq-local ha-eat-terminal eat-terminal)))
(t (switch-to-buffer
(apply 'make-term (append (list name program startfile)
switches))))))
#+END_SRC
** Interactive Interface to Remote Systems ** Interactive Interface to Remote Systems
The function, =ha-ssh= pops up a list of /favorite hosts/ and then uses the =vterm= functions to automatically SSH into the chosen host: The function, =ha-ssh= pops up a list of /favorite hosts/ and then uses the =vterm= functions to automatically SSH into the chosen host:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh (hostname &optional window-name) (defun ha-ssh (hostname &optional window-name)
"Start a SSH session to a given HOSTNAME (with an optionally specified WINDOW-NAME). "Start a SSH session to a given HOSTNAME (with an optionally specified WINDOW-NAME).
If called interactively, it presents the user with a list If called interactively, it presents the user with a list
returned by =ha-ssh-choose-host=." returned by =ha-ssh-choose-host=."
(interactive (list (ha-ssh-choose-host))) (interactive (list (ha-ssh-choose-host)))
(unless window-name (unless window-name
(setq window-name (format "ssh: %s" hostname))) (setq window-name (format "ssh: %s" hostname)))
(setq ha-latest-ssh-window-name (format "*%s*" window-name)) (setq ha-latest-ssh-window-name (format "*%s*" window-name))
(ha-make-term window-name "ssh" nil hostname)
;; I really like this =vterm= interface, so if I've got it loaded, let's use it: (pop-to-buffer ha-latest-ssh-window-name))
(if (not (fboundp 'vterm))
;; Should we assume the =ssh= we want is on the PATH that started Emacs?
(make-term window-name "ssh" nil hostname)
(vterm ha-latest-ssh-window-name)
(vterm-send-string (format "ssh %s" hostname))
(vterm-send-return))
(pop-to-buffer ha-latest-ssh-window-name))
#+end_src #+end_src
Of course, we need a function that =interactive= can call to get that list, and my thought is to call =helm= if it is available, otherwise, assume that ido/ivy will take over the =completing-read= function: Of course, we need a function that =interactive= can call to get that list, and my thought is to call =helm= if it is available, otherwise, assume that ido/ivy will take over the =completing-read= function:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-choose-host () (defun ha-ssh-choose-host ()
"Prompts the user for a host, and if it is in the cache, return "Prompts the user for a host, and if it is in the cache, return
its IP address, otherwise, return the input given. its IP address, otherwise, return the input given.
This is used in calls to =interactive= to select a host." This is used in calls to =interactive= to select a host."
(let ((hostname (completing-read-alist "Hostname: " ha-ssh-favorite-hostnames nil 'confirm
;; We call Helm directly if installed, only so that we can get better nil 'ha-ssh-host-history))
;; labels in the window, otherwise, the =completing-read= call would be fine.
(if (fboundp 'helm-comp-read)
(helm-comp-read "Hostname: " ha-ssh-favorite-hostnames
:name "Hosts"
:fuzzy t :history ha-ssh-host-history)
(completing-read "Hostname: " ha-ssh-favorite-hostnames nil 'confirm nil 'ha-ssh-host-history))))
(alist-get hostname ha-ssh-favorite-hostnames hostname nil 'equal)))
#+end_src
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 name)
"Creates and tidies up a =vterm= terminal shell in side window."
(interactive (list (read-directory-name "Starting Directory: " (project-root (project-current)))))
(let* ((win-name (or name (ha-shell--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 #+end_src
Before we leave this section, I realize that I would like a way to /add/ to my list of hosts: Before we leave this section, I realize that I would like a way to /add/ to my list of hosts:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-add-favorite-host (hostname ip-address) (defun ha-ssh-add-favorite-host (hostname ip-address)
"Add a favorite host to your list for easy pickin's." "Add a favorite host to your list for easy pickin's."
(interactive "sHostname: \nsIP Address: ") (interactive "sHostname: \nsIP Address: ")
(add-to-list 'ha-ssh-favorite-hostnames (cons hostname ip-address))) (add-to-list 'ha-ssh-favorite-hostnames (cons hostname ip-address)))
#+end_src
** Programmatic Interface
For the sake of my demonstrations, I use =ha-shell= to start a terminal with a particular =name=. Then, I can send commands into it.
#+begin_src emacs-lisp
(defun ha-shell (&optional directory name)
"Creates a terminal window using `ha-make-term'.
Stores the name, for further calls to `ha-shell-send', and
`ha-shell-send-lines'."
(interactive (list (read-directory-name "Starting Directory: " (project-root (project-current)))))
(let* ((default-directory (or directory default-directory))
(win-name (or name (replace-regexp-in-string (rx (+? any)
(group (1+ (not "/")))
(optional "/") eol)
"\\1"
default-directory)))
(buf-name (format "*%s*" win-name)))
(setq ha-latest-ssh-window-name buf-name)
(ha-make-term win-name ha-shell))) ; Lisp-2 FTW!?
#+end_src
Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ send commands to the running terminal, e.g. =(ha-shell-send "ls *.py")= I would really like to be able to send and execute a command in a terminal from a script.
#+begin_src emacs-lisp
(defun ha-shell-send (command &optional name)
"Send COMMAND to existing shell terminal based on DIRECTORY.
If you want to refer to another session, specify the correct NAME.
This is really useful for scripts and demonstrations."
(unless name
(setq name ha-latest-ssh-window-name))
(save-window-excursion
(pop-to-buffer name)
(goto-char (point-max))
(cond
((eq major-mode 'vterm-mode) (progn
(vterm-send-string command)
(vterm-send-return)))
((eq major-mode 'eat-mode) (eat-term-send-string
ha-eat-terminal (concat command "\n")))
(t (progn
(insert command)
(term-send-input))))))
#+end_src #+end_src
Let's have a quick way to bugger out of the terminal: Let's have a quick way to bugger out of the terminal:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-exit (&optional window-name) (defun ha-shell-exit (&optional name)
"End the SSH session specified by WINDOW-NAME (or if not, the latest session)." "End the SSH session specified by NAME (or if not, the latest session)."
(interactive) (interactive)
(unless (string-match-p "v?term" (buffer-name)) (unless (or (eq major-mode 'vterm-mode) ; Already in a term?
(unless window-name (eq major-mode 'eat-mode) ; Just close this.
(setq window-name ha-latest-ssh-window-name)) (eq major-mode 'term-mode))
(pop-to-buffer window-name)) (unless name
(setq name ha-latest-ssh-window-name))
(pop-to-buffer name))
(ignore-errors (ignore-errors
(term-send-eof)) (term-send-eof))
(kill-buffer window-name) (kill-buffer name)
(delete-window)) (delete-window))
#+end_src
** Programmatic Interface
Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ send commands to the running terminal, e.g. =(ha-shell-send "ls *.py")=
Since every project perspective may have a shell terminal, lets see if I can figure which shell buffer to send—based on the =current-directory=.
#+begin_src emacs-lisp
(defun ha-shell-send (command &optional directory)
"Send COMMAND to existing shell terminal based on DIRECTORY.
If the shell doesn't already exist, start on up by calling
the `ha-shell' function.
The real work for this is done by `ha-ssh-send'.
If DIRECTORY is nil, use the project root from project."
(let ((buf (ha-shell--buf-from-dir directory)))
(unless buf
(setq buf (ha-shell directory)))
(ha-ssh-send command buf)))
(defun ha-shell--buf-from-dir (directory)
"Return Terminal buffer associated with DIRECTORY.
Or nil if no buffer has been found."
(let* ((win-name (ha-shell--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))))
(first bufs)))
(defun ha-shell--name-from-dir (&optional directory)
"Return an appropriate title for a terminal based on DIRECTORY.
If DIRECTORY is nil, use the `project-name'."
(unless directory
(setq directory (project-name (project-current))))
(let ((name
;; Most of the time I just want the base project name, but in
;; my "work" directory, the projects are too similar, and I
;; need two levels of directories to distinguish them as a
;; project.
(if (s-contains? "/work/" directory)
(thread-last directory
(s-split "/")
(-remove 's-blank-str?)
(-take-last 2)
(s-join "/"))
(file-name-base (directory-file-name directory)))))
(format "Terminal: %s" name)))
#+end_src #+end_src
Perhaps a Unit test is in order: For example:
#+begin_src emacs-lisp :tangle no
(ert-deftest ha--terminal-name-from-dir-test ()
(should
(string= (ha-shell--name-from-dir "~/src/hamacs/") "Terminal: hamacs"))
(should
(string= (ha-shell--name-from-dir "~/work/foo/bar") "Terminal: foo/bar"))
(should
(string= (ha-shell--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 :tangle no
#+begin_src emacs-lisp (ha-shell)
(defun ha-ssh-send (phrase &optional window-name) (ha-shell-send "date")
"Send command PHRASE to the currently running SSH instance. (ha-shell-exit)
If you want to refer to another session, specify the correct WINDOW-NAME. #+END_SRC
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)
(if (fboundp 'vterm)
(progn
(vterm-send-string phrase)
(vterm-send-return))
(progn
(term-send-raw-string phrase)
(term-send-input)))))
#+end_src
As you may know, Im big into /literate devops/ where I put my shell commands in org files. However, I also work as part of a team that for some reason, doesnt accept Emacs as their One True Editor. At least, I am able to talk them into describing commands in Markdown files, e.g. =README.md=. Instead of /copying-pasting/ into the shell, could I /send/ the /current command/ to that shell? As you may know, Im big into /literate devops/ where I put my shell commands in org files. However, I also work as part of a team that for some reason, doesnt accept Emacs as their One True Editor. At least, I am able to talk them into describing commands in Markdown files, e.g. =README.md=. Instead of /copying-pasting/ into the shell, could I /send/ the /current command/ to that shell?
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-send-line (prefix) (defun ha-shell-send-line (prefix &optional name)
"Copy the contents of the current line in the current buffer, "Copy the contents of the current line in the current buffer,
and call =ha-ssh-send= with it. After sending the contents, it and call `ha-sshell-send' with it. After sending the contents, it
returns to the current line." returns to the current location. PREFIX is the number of lines."
(interactive "P") (interactive "P")
;; The function =save-excursion= doesn't seem to work... (dolist (line (ha-ssh--line-or-block prefix))
(let ((buf (current-buffer))) ;; (sit-for 0.25)
(dolist (line (ha-ssh--line-or-block prefix)) (ha-shell-send line)))
;; (sit-for 0.25)
(ha-ssh-send line))
(pop-to-buffer buf)))
#+end_src #+end_src
What does /current command/ mean? The current line? A good fall back. Selected region? Sure, if active, but that seems like more work. In a Markdown file, I can gather the entire source code block, just like in an Org file. What does /current command/ mean? The current line? A good fall back. Selected region? Sure, if active, but that seems like more work. In a Markdown file, I can gather the entire source code block, just like in an Org file.
So the following function may be a bit complicated in determining what is this /current code/: So the following function may be a bit complicated in determining what is this /current code/:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh--line-or-block (num-lines) (defun ha-ssh--line-or-block (num-lines)
"Return a list of the NUM-LINES from current buffer. "Return a list of the NUM-LINES from current buffer.
@ -382,6 +408,7 @@ So the following function may be a bit complicated in determining what is this /
#+end_src #+end_src
In Markdown (and org), I might have initial spaces that should be removed (but not all initial spaces): In Markdown (and org), I might have initial spaces that should be removed (but not all initial spaces):
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh--line-cleanup (str) (defun ha-ssh--line-cleanup (str)
"Return STR as a list of strings." "Return STR as a list of strings."
@ -392,43 +419,47 @@ In Markdown (and org), I might have initial spaces that should be removed (but n
(trim-amount (when (string-match (rx bol (group (* space))) first-line) (trim-amount (when (string-match (rx bol (group (* space))) first-line)
(length (match-string 1 first-line))))) (length (match-string 1 first-line)))))
(mapcar (lambda (line) (substring line trim-amount)) lst-contents))) (mapcar (lambda (line) (substring line trim-amount)) lst-contents)))
#+end_src
And some tests to validate:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest ha-ssh--line-cleanup-test () (ert-deftest ha-ssh--line-cleanup-test ()
(should (equal (ha-ssh--line-cleanup "bob") '("bob"))) (should (equal (ha-ssh--line-cleanup "bob") '("bob")))
(should (equal (ha-ssh--line-cleanup " bob") '("bob"))) (should (equal (ha-ssh--line-cleanup " bob") '("bob")))
(should (equal (ha-ssh--line-cleanup "bob\nfoo") '("bob" "foo"))) (should (equal (ha-ssh--line-cleanup "bob\nfoo") '("bob" "foo")))
(should (equal (ha-ssh--line-cleanup " bob\n foo") '("bob" "foo"))) (should (equal (ha-ssh--line-cleanup " bob\n foo") '("bob" "foo")))
(should (equal (ha-ssh--line-cleanup " bob\n foo") '("bob" " foo")))) (should (equal (ha-ssh--line-cleanup " bob\n foo") '("bob" " foo"))))
#+end_src #+END_SRC
** Editing Remote Files ** Editing Remote Files
TRAMP, when it works, is amazing that we can give it a reference to a remote directory, and have =find-file= magically autocomplete. TRAMP, when it works, is amazing that we can give it a reference to a remote directory, and have =find-file= magically autocomplete.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-find-file (hostname) (defun ha-ssh-find-file (hostname)
"Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=." "Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=."
(interactive (list (ha-ssh-choose-host))) (interactive (list (ha-ssh-choose-host)))
(let ((tramp-ssh-ref (format "/ssh:%s:" hostname)) (let ((tramp-ssh-ref (format "/ssh:%s:" hostname))
(other-window (when (equal current-prefix-arg '(4)) t))) (other-window (when (equal current-prefix-arg '(4)) t)))
(ha-ssh--find-file tramp-ssh-ref other-window))) (ha-ssh--find-file tramp-ssh-ref other-window)))
(defun ha-ssh--find-file (tramp-ssh-ref &optional other-window) (defun ha-ssh--find-file (tramp-ssh-ref &optional other-window)
"Calls =find-file= after internally completing a file reference based on TRAMP-SSH-REF." "Calls =find-file= after internally completing a file reference based on TRAMP-SSH-REF."
(let ((tramp-file (read-file-name "Find file: " tramp-ssh-ref))) (let ((tramp-file (read-file-name "Find file: " tramp-ssh-ref)))
(if other-window (if other-window
(find-file-other-window tramp-file) (find-file-other-window tramp-file)
(find-file tramp-file)))) (find-file tramp-file))))
#+end_src #+end_src
We can even edit it as root: We can even edit it as root:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-find-root (hostname) (defun ha-ssh-find-file-root (hostname)
"Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=." "Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=."
(interactive (list (ha-ssh-choose-host))) (interactive (list (ha-ssh-choose-host)))
(let ((tramp-ssh-ref (format "/ssh:%s|sudo:%s:" hostname hostname)) (let ((tramp-ssh-ref (format "/ssh:%s|sudo:%s:" hostname hostname))
(other-window (when (equal current-prefix-arg '(4)) t))) (other-window (when (equal current-prefix-arg '(4)) t)))
(ha-ssh--find-file tramp-ssh-ref other-window))) (ha-ssh--find-file tramp-ssh-ref other-window)))
#+end_src #+end_src
** OpenStack Interface ** OpenStack Interface
@ -438,22 +469,13 @@ Instead of making sure I have a list of remote systems already in the favorite h
We'll give =openstack= CLI a =--format json= option to make it easier for parsing: We'll give =openstack= CLI a =--format json= option to make it easier for parsing:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package json) (use-package json)
#+end_src #+end_src
Need a variable to hold all our interesting hosts. Notice I use the word /overcloud/, but this is a name I've used for years to refer to /my virtual machines/ that I can get a listing of, and not get other VMs that I don't own.
#+begin_src emacs-lisp
(defvar ha-ssh-overcloud-cache-data nil
"A vector of associated lists containing the servers in an Overcloud.")
#+end_src
If our cache data is empty, we could automatically retrieve this information, but only on the first time we attempt to connect. To do this, we'll =advice= the =ha-ssh-choose-host= function defined earlier:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-overcloud-query-for-hosts () (defun ha-ssh-overcloud-query-for-hosts ()
"If the overcloud cache hasn't be populated, ask the user if we want to run the command." "If the overcloud cache hasn't be populated, ask the user if we want to run the command."
(when (not ha-ssh-overcloud-cache-data) (when (not ha-ssh-favorite-hostnames)
(when (y-or-n-p "Cache of Overcloud hosts aren't populated. Retrieve hosts?") (when (y-or-n-p "Cache of Overcloud hosts aren't populated. Retrieve hosts?")
(call-interactively 'ha-ssh-overcloud-cache-populate)))) (call-interactively 'ha-ssh-overcloud-cache-populate))))
@ -466,9 +488,9 @@ We'll do the work of getting the /server list/ with this function:
(defun ha-ssh-overcloud-cache-populate (cluster) (defun ha-ssh-overcloud-cache-populate (cluster)
"Given an `os-cloud' entry, stores all available hostnames. "Given an `os-cloud' entry, stores all available hostnames.
Calls `ha-ssh-add-favorite-host' for each host found." Calls `ha-ssh-add-favorite-host' for each host found."
(interactive (list (completing-read "Cluster: " '(devprod1 devprod501 devprod502)))) (interactive (list (completing-read "Cluster: " '(devprod501 devprod502))))
(message "Calling the `openstack' command...this will take a while. Grab a coffee, eh?") (message "Calling the `openstack' command...this will take a while. Grab a coffee, eh?")
(let* ((command (format "openstack --os-cloud %s server list --no-name-lookup --insecure -f json" cluster)) (let* ((command (format "openstack --os-cloud %s server list --no-name-lookup -f json" cluster))
(json-data (thread-last command (json-data (thread-last command
(shell-command-to-string) (shell-command-to-string)
(json-read-from-string)))) (json-read-from-string))))
@ -481,38 +503,23 @@ We'll do the work of getting the /server list/ with this function:
(message "Call to `openstack' complete. Found %d hosts." (length json-data)))) (message "Call to `openstack' complete. Found %d hosts." (length json-data))))
#+end_src #+end_src
In case I change my virtual machines, I can repopulate that cache:
#+begin_src emacs-lisp
(defun ha-ssh-overcloud-cache-repopulate ()
"Repopulate the cache based on redeployment of my overcloud."
(interactive)
(setq ha-ssh-overcloud-cache-data nil)
(call-interactively 'ha-ssh-overcloud-cache-populate))
#+end_src
The primary interface: The primary interface:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-ssh-overcloud (hostname) (defun ha-ssh-overcloud (hostname)
"Log into an overcloud host given by HOSTNAME. Works better if "Log into an overcloud host given by HOSTNAME. Works better if
you have previously run =ssh-copy-id= on the host. Remember, to you have previously run =ssh-copy-id= on the host. Remember, to
make it behave like a real terminal (instead of a window in make it behave like a real terminal (instead of a window in
Emacs), hit =C-c C-k=." Emacs), hit =C-c C-k=."
(interactive (list (ha-ssh-choose-host))) (interactive (list (ha-ssh-choose-host)))
(when (not (string-match-p "\." hostname)) (when (not (string-match-p "\." hostname))
(setq hostname (format "%s.%s" hostname (getenv "OS_PROJECT_NAME")))) (setq hostname (format "%s.%s" hostname (getenv "OS_PROJECT_NAME"))))
(let ((window-label (or (-some->> ha-ssh-favorite-hostnames (let ((window-label (or (thread-last ha-ssh-favorite-hostnames
(rassoc hostname) (rassoc hostname)
car) (car))
hostname))) hostname)))
(ha-ssh hostname window-label) (ha-ssh hostname window-label)))
(sit-for 1)
(ha-ssh-send "sudo -i")
(ha-ssh-send (format "export PS1='\\[\\e[34m\\]%s\\[\e[m\\] \\[\\e[33m\\]\\$\\[\\e[m\\] '"
window-label))
(ha-ssh-send "clear")))
#+end_src #+end_src
* Keybindings * Keybindings
This file, so far, as been good-enough for a Vanilla Emacs installation, but to hook into Doom's leader for some sequence binding, this code isn't: This file, so far, as been good-enough for a Vanilla Emacs installation, but to hook into Doom's leader for some sequence binding, this code isn't:
@ -523,13 +530,11 @@ This file, so far, as been good-enough for a Vanilla Emacs installation, but to
"a s o" '("overcloud" . ha-ssh-overcloud) "a s o" '("overcloud" . ha-ssh-overcloud)
"a s l" '("local shell" . ha-shell) "a s l" '("local shell" . ha-shell)
"a s s" '("remote shell" . ha-ssh) "a s s" '("remote shell" . ha-ssh)
"a s p" '("project shell" . (lambda () (interactive) (ha-shell (project-root (project-current))))) "a s p" '("project shell" . eat-project)
"a s q" '("quit shell" . ha-ssh-exit) "a s q" '("quit shell" . ha-ssh-exit)
"a s f" '("find-file" . ha-ssh-find-file) "a s f" '("find-file" . ha-ssh-find-file)
"a s r" '("find-root" . ha-ssh-find-root) "a s r" '("find-root" . ha-ssh-find-root)
"a s b" '("send line" . ha-ssh-send-line) "a s b" '("send line" . ha-ssh-send-line))
"p t" '("project vterm" . (lambda () (interactive) (ha-shell (project-root (project-current))))))
#+end_src #+end_src
* Technical Artifacts :noexport: * Technical Artifacts :noexport:
Provide a name so we can =require= the file: Provide a name so we can =require= the file:

111
pud.org
View file

@ -2,7 +2,7 @@
#+author: Howard X. Abrams #+author: Howard X. Abrams
#+date: 2025-01-18 #+date: 2025-01-18
#+filetags: emacs hamacs #+filetags: emacs hamacs
#+lastmod: [2025-03-01 Sat] #+lastmod: [2025-03-03 Mon]
A literate programming file for a Comint-based MUD client. A literate programming file for a Comint-based MUD client.
@ -48,19 +48,21 @@ The default connects to *Moss n Puddles*, my own MUD which I invite you to jo
:group 'processes) :group 'processes)
(defcustom pud-worlds (defcustom pud-worlds
'(["Moss-n-Puddles" "howardabrams.com" 4000 "" ""]) '(["Moss-n-Puddles" telnet "howardabrams.com" 4000])
"List of worlds you play in. "List of worlds you play in.
You need to define the worlds you play in before you can get You need to define the worlds you play in before you can get
started. In most worlds, you can start playing using a guest account. started. In most worlds, you can start playing using a guest account.
Each element WORLD of the list has the following form: Each element WORLD of the list has the following form:
\[NAME HOST PORT CHARACTER PASSWORD CONNECTION-STR] \[CONN-TYPE NAME HOST PORT CHARACTER PASSWORD LOGIN-STR]
NAME identifies the connection, HOST and PORT specify the network NAME identifies the connection, HOST and PORT specify the network
connection, CHARACTER and PASSWORD are used to connect automatically. connection, CHARACTER and PASSWORD are used to connect automatically.
The CONNECTION-STR is a string with two `%s' where this substitutes The CONN-TYPE can be either 'telnet or 'ssh.
The LOGIN-STR is a string with two `%s' where this substitutes
the username and password respectively. Sends this to the server after the username and password respectively. Sends this to the server after
establishing a connection. This can be blank for the default. establishing a connection. This can be blank for the default.
If given, make sure to have a trailing `\n' to automatically send. If given, make sure to have a trailing `\n' to automatically send.
@ -70,11 +72,19 @@ The default connects to *Moss n Puddles*, my own MUD which I invite you to jo
:type '(repeat :type '(repeat
(vector :tag "Server World" (vector :tag "Server World"
(string :tag "Name") (string :tag "Name")
(string :tag "Host") (radio :tag "Type"
(integer :tag "Port") (const :tag "Telnet" :value telnet)
(string :tag "Char" :value "guest") (const :tag "SSH" :value ssh))
(string :tag "Pass") (string :tag "Hostname")
(string :tag "Connect String" :value "connect %s %s"))) (integer :tag "Port num")
(string :tag "Username" :value "guest")
(string :tag "Password")
(string :tag "Login String"
:format "%t: %v%h"
:doc "The login string to send after connection.
This should probably have a \`\\n' at the end to submit it.
If blank or nil, use the \`pud-default-connection-string'.
For example: connect %s %s\\n")))
:group 'pud) :group 'pud)
#+END_SRC #+END_SRC
@ -84,24 +94,23 @@ For instance:
(use-package pud (use-package pud
:custom :custom
(pud-worlds (pud-worlds
'(["Remote Moss-n-Puddles" "howardabrams" 4000 "bobby"] '(["Remote Moss-n-Puddles" 'ssh "howardabrams.com" 4000 "bobby"]
; ↑ No password? Should be in .authinfo.gpg ; ↑ No password? Should be in .authinfo.gpg
["Local Root" "localhost" 4000 "suzy" "some-pass"] ["Local Root" 'telnet "localhost" 4000 "suzy" "some-pass"]
; ↑ This has the password in your custom settings. ; ↑ This has the password in your custom settings.
; ↓ Password from authinfo, special connection string: ; ↓ Password from authinfo, special connection string:
["Local User" "localhost" 4000 "rick" nil "login %s %s"]))) ["Local User" 'telnet "localhost" 4000 "rick" nil "login %s %s"])))
#+END_SRC #+END_SRC
Hidden: Hidden:
#+BEGIN_SRC emacs-lisp :tangle no :eval no #+BEGIN_SRC emacs-lisp :tangle no :eval no
(setq pud-worlds (setq pud-worlds
'(["moss-n-puddles" "howardabrams.com" 4000 "howard"] '(["Moss-n-Puddles" ssh "howardabrams.com" 4004 "howard" "" "\\nconnect %s %s\\n"]
["moss-n-puddles" "howardabrams.com" 4000 "rick"] ["Moss-n-Puddles" ssh "howardabrams.com" 4004 "rick" "" "\\nconnect %s %s\\n"]
["moss-n-puddles" "howardabrams.com" 4000 "darol"] ["Local-Moss" telnet "localhost" 4000 "howard" "" ""]
["local-evennia" "localhost" 4000 "howard"] ["Local-Moss" telnet "localhost" 4000 "rick" "" ""]))
["local-evennia" "localhost" 4000 "rick"]))
#+END_SRC #+END_SRC
Seems like MUDs have a standard login sequence, but they dont have to. Here is the default that a user can override in their =pud-worlds= listing: Seems like MUDs have a standard login sequence, but they dont have to. Here is the default that a user can override in their =pud-worlds= listing:
@ -109,7 +118,7 @@ Seems like MUDs have a standard login sequence, but they dont have to. Here i
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defcustom pud-default-connection-string "connect %s %s\n" (defcustom pud-default-connection-string "connect %s %s\n"
"The standard connection string to substitute the username and password." "The standard connection string to substitute the username and password."
:type '(string :tag "Connect String" :value "connect %s %s\n") :type '(string)
:group 'pud) :group 'pud)
#+END_SRC #+END_SRC
@ -161,14 +170,14 @@ The following functions are accessibility functions to the world entry.
(defun pud-world-name (world) (defun pud-world-name (world)
"Return the name for WORLD as a string." "Return the name for WORLD as a string."
(if (vectorp world) (if (vectorp world)
(if (or (length< world 4) (null (aref world 3)) (string-blank-p (aref world 3))) (if (or (length< world 5) (null (aref world 4)) (string-blank-p (aref world 4)))
(aref world 0) (aref world 0)
(concat (aref world 3) "@" (aref world 0))) (concat (aref world 4) "@" (aref world 0)))
world)) world))
(defun pud-world-network (world) (defun pud-world-network (world)
"Return the network details for WORLD as a cons cell (HOST . PORT)." "Return the network details for WORLD as a cons cell (HOST . PORT)."
(list (aref world 1) (format "%s" (aref world 2)))) (list (aref world 2) (format "%s" (aref world 3))))
(defun pud-world-creds (world) (defun pud-world-creds (world)
"Return the username and password from WORLD. "Return the username and password from WORLD.
@ -182,7 +191,7 @@ The following functions are accessibility functions to the world entry.
(list (plist-get auth-results :user) (list (plist-get auth-results :user)
(funcall (plist-get auth-results :secret))) (funcall (plist-get auth-results :secret)))
;; No match? Just return values from world: ;; No match? Just return values from world:
(list (aref world 3) (aref world 4))))) (list (aref world 4) (aref world 5)))))
#+END_SRC #+END_SRC
And some basic functions I should expand. And some basic functions I should expand.
@ -196,22 +205,25 @@ And some basic functions I should expand.
(should (string-equal (pud-world-name ["foobar" "localhost" "4000" "guest" "guest"]) "guest@foobar"))) (should (string-equal (pud-world-name ["foobar" "localhost" "4000" "guest" "guest"]) "guest@foobar")))
(ert-deftest pud-world-network-test () (ert-deftest pud-world-network-test ()
(should (equal (pud-world-network ["foobar" "overthere" "4000" "guest" "guest"]) '("overthere" "4000"))) (should (equal (pud-world-network ["foobar" telnet "overthere" "4000" "guest" "guest"]) '("overthere" "4000")))
(should (equal (pud-world-network ["foobar" "overthere" 4000 "guest" "guest"]) '("overthere" "4000")))) (should (equal (pud-world-network ["foobar" ssh "overthere" 4000 "guest" "guest"]) '("overthere" "4000"))))
(ert-deftest pud-world-creds-test () (ert-deftest pud-world-creds-test ()
;; Test with no match in authinfo! ;; Test with no match in authinfo!
(should (equal (should (equal
(pud-world-creds ["first" "some-home" 4000 "a-user" "a-pass"]) (pud-world-creds ["some-place" telnet "some-home" 4000 "a-user" "a-pass"])
'("a-user" "a-pass"))) '("a-user" "a-pass")))
;; This test works if the following line is in .authinfo: ;; This test works if the following line is in .authinfo:
;; machine localhost port 4000 login george password testpass ;; machine localhost port 4000 login george password testpass
(should (equal (should (equal
(pud-world-creds ["first" "localhost" 4000 "george"]) (pud-world-creds ["nudder-place" ssh "localhost" 4000 "george"])
'("george" "testpass")))) '("george" "testpass"))))
#+END_SRC #+END_SRC
* Basics * Basics
:LOGBOOK:
CLOCK: [2025-03-03 Mon 11:57]--[2025-03-03 Mon 12:10] => 0:13
:END:
Using Comint, and hoping to have the ANSI colors displayed. Using Comint, and hoping to have the ANSI colors displayed.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@ -222,17 +234,51 @@ Using Comint, and hoping to have the ANSI colors displayed.
Im going to use good ol fashion =telnet= for the connection: Im going to use good ol fashion =telnet= for the connection:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defvar pud-cli-file-path "telnet" ; ssh!? (defcustom pud-telnet-path "telnet"
"Path to the program used by `run-pud'.") "Path to the program used by `run-pud' to connect using telnet."
:type '(string)
:group 'pud)
(defcustom pud-ssh-path "ssh"
"Path to the program used by `run-pud' to connect using ssh."
:type '(string)
:group 'pud)
#+END_SRC #+END_SRC
The pud-cli-arguments, holds a list of commandline arguments: the port. The pud-cli-arguments, holds a list of commandline arguments: the port.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defvar pud-cli-arguments nil (defvar pud-cli-arguments nil
"A list of arguments to use before the telnet location.") "A list of arguments to use before the connection.")
#+END_SRC #+END_SRC
Command string to use, given a =world= with a connection type:
#+BEGIN_SRC emacs-lisp
(defun pud-cli-command (world)
"Return a command string to pass to the shell.
The WORLD is a vector with the hostname, see `pud-worlds'."
(seq-let (host port) (pud-world-network world)
(message "Dealing with: %s %s %s" host port (aref world 1))
(cl-case (aref world 1)
(telnet (append (cons pud-telnet-path pud-cli-arguments)
(list host port)))
(ssh (append (cons pud-cli-filepath-ssh pud-cli-arguments)
(list "-p" port host)))
(t (error "Unsupported connection type")))))
#+END_SRC
Some tests:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest pud-cli-command-test ()
(should (equal (pud-cli-command ["some-world" telnet "world.r.us" 4000])
'("telnet" "world.r.us" "4000")))
(should (equal (pud-cli-command ["nudder-world" ssh "world.r.us" 4004])
'("ssh" "-p" "4004" "world.r.us"))))
#+END_SRC
The empty and currently disused mode map for storing our custom keybindings inherits from =comint-mode-map=, so we get the same keys exposed in =comint-mode=. The empty and currently disused mode map for storing our custom keybindings inherits from =comint-mode-map=, so we get the same keys exposed in =comint-mode=.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@ -271,8 +317,7 @@ The main entry point to the program is the =run-pud= function:
- username (can be overridden) - username (can be overridden)
- password (should be overridden)" - password (should be overridden)"
(interactive (list (pud-get-world))) (interactive (list (pud-get-world)))
(let* ((pud-program pud-cli-file-path) (let* ((pud-cli (pud-cli-command world))
(pud-args (append pud-cli-arguments (pud-world-network world)))
(buffer (get-buffer-create (pud-buffer-name world))) (buffer (get-buffer-create (pud-buffer-name world)))
(proc-alive (comint-check-proc buffer)) (proc-alive (comint-check-proc buffer))
(process (get-buffer-process buffer))) (process (get-buffer-process buffer)))
@ -280,7 +325,7 @@ The main entry point to the program is the =run-pud= function:
;; mode. ;; mode.
(unless proc-alive (unless proc-alive
(with-current-buffer buffer (with-current-buffer buffer
(apply 'make-comint-in-buffer "Pud" buffer pud-program nil pud-args) (apply 'make-comint-in-buffer "Pud" buffer (car pud-cli) nil (cdr pud-cli))
(pud-mode) (pud-mode)
(visual-line-mode 1) (visual-line-mode 1)
(pud-reconnect world))) (pud-reconnect world)))

View file

@ -0,0 +1,4 @@
# key: <ill
# name: ignore-line-length
# --
<!--- pyml disable-num-lines ${0} line-length -->

View file

@ -0,0 +1,4 @@
# key: <ils
# name: ignore-lines
# --
<!--- pyml disable-num-lines ${1} ${2:$$(yas-choose-value '("line-length" "no-bare-urls" "code-fence-style" ))} -->

View file

@ -0,0 +1,4 @@
# key: <inl
# name: ignore-next-line
# --
<!--- pyml disable-next-line ${1:$$(yas-choose-value '("line-length" "no-bare-urls" "code-fence-style" ))} -->

View file

@ -0,0 +1,5 @@
# -*- mode: snippet -*-
# name: ignore-lines
# key: <ils
# --
<!--- pyml disable-num-lines ${1} ${2:$$(yas-choose-value '("line-length" "no-bare-urls" "code-fence-style" ))} -->

View file

@ -0,0 +1,6 @@
# key: <sy
# name: yaml-code-block
# --
#+BEGIN_SRC yaml
$0
#+END_SRC