Compare commits
5 commits
30af02232a
...
6155e58879
Author | SHA1 | Date | |
---|---|---|---|
|
6155e58879 | ||
|
2f1a517391 | ||
|
9ab67ce2d1 | ||
|
ac4d2cbc0b | ||
|
6a28307eca |
13 changed files with 481 additions and 377 deletions
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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 isn’t /package-specific/.
|
I begin configuration of Emacs that isn’t /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 Choi’s [[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. I’ve 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 don’t remember appear.
|
I like Charles Choi’s [[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. I’ve 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 don’t 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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 I’ve switch to [[https://github.com/zkry/yaml-pro][yaml-pro]] that is now based on Tree Sitter. Let’s make sure the Tree-Sitter version works:
|
, so I’ve switch to [[https://github.com/zkry/yaml-pro][yaml-pro]] that is now based on Tree Sitter. Let’s 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= doesn’t screw things up.
|
We need to make sure the =mixed-pitch-mode= doesn’t 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 let’s 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 let’s 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 Ansible’s 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 Ansible’s 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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
445
ha-remoting.org
445
ha-remoting.org
|
@ -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 doesn’t require a dedicate library that requires re-compilation. While offering [[https://elpa.nongnu.org/nongnu-devel/doc/eat.html][online documentation]], I’m 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 I’d 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, let’s 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, I’m 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, doesn’t 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, I’m 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, doesn’t 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
111
pud.org
|
@ -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 don’t 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 don’t 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 don’t 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.
|
||||||
I’m going to use good ‘ol fashion =telnet= for the connection:
|
I’m 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)))
|
||||||
|
|
4
snippets/markdown-mode/ignore-line-length
Normal file
4
snippets/markdown-mode/ignore-line-length
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# key: <ill
|
||||||
|
# name: ignore-line-length
|
||||||
|
# --
|
||||||
|
<!--- pyml disable-num-lines ${0} line-length -->
|
4
snippets/markdown-mode/ignore-lines
Normal file
4
snippets/markdown-mode/ignore-lines
Normal 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" ))} -->
|
4
snippets/markdown-mode/ignore-next-line
Normal file
4
snippets/markdown-mode/ignore-next-line
Normal 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" ))} -->
|
5
snippets/markdown-mode/yaml
Normal file
5
snippets/markdown-mode/yaml
Normal 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" ))} -->
|
6
snippets/org-mode/yaml-code-block
Normal file
6
snippets/org-mode/yaml-code-block
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# key: <sy
|
||||||
|
# name: yaml-code-block
|
||||||
|
# --
|
||||||
|
#+BEGIN_SRC yaml
|
||||||
|
$0
|
||||||
|
#+END_SRC
|
Loading…
Reference in a new issue