2023-12-03 18:57:36 +00:00
#+title : General Programming Configuration
#+author : Howard X. Abrams
#+date : 2020-10-26
#+tags : emacs programming yaml ansible docker json
2021-11-09 00:02:13 +00:00
A literate programming file for helping me program.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; general-programming --- Configuration for general languages. -*- lexical-binding: t; -* -
;;
2023-02-23 17:35:36 +00:00
;; © 2020-2023 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
;; Maintainer: Howard X. Abrams
;; Created: October 26, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
2023-12-20 04:11:35 +00:00
;; ~/other/hamacs/ha-programming.org
2022-03-09 18:45:37 +00:00
;; And tangle the file to recreate this one.
;;
;;; Code:
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
* Introduction
2023-05-01 18:49:33 +00:00
Configuration for programming interfaces and workflows that behave similarly.
2021-11-09 00:02:13 +00:00
* General
The following work for all programming languages.
2024-04-30 00:30:45 +00:00
** Mise
Combining my use of programming virtual environments with =direnv= and =Makefile= , the [[http://mise.jdx.dev ][Mise-en-Place ]] project has [[https://github.com/liuyinz/mise.el ][an Emacs mode ]]:
#+begin_src emacs-lisp
(use-package mise
:straight (:host github :repo "liuyinz/mise.el")
:config (global-mise-mode))
#+end_src
This requires an underlying program to be installed. On my Linux system, I issued:
#+begin_src sh
apt update -y && apt install -y gpg sudo wget curl
sudo install -dm 755 /etc/apt/keyrings
wget -qO - https://mise.jdx.dev/gpg-key.pub | gpg --dearmor | sudo tee /etc/apt/keyrings/mise-archive-keyring.gpg 1> /dev/null
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.gpg arch=amd64] https:/ /mise.jdx.dev/deb stable main" | sudo tee /etc/apt/sources.list.d/mise.list
sudo apt update
sudo apt install -y mise
#+end_src
And on the MacOS:
#+begin_src sh :results silent
brew install mise
#+end_src
I have the following configured in my home directory, [[file:~/.config/mise.toml ][~/.config/mise.toml ]] :
#+begin_src toml :tangle ~/.config/mise/config.toml
[env]
# supports arbitrary env vars so mise can be used like direnv/dotenv
# NODE_ENV = 'production'
[tools]
# specify single or multiple versions
ruby = '2.6.6'
# erlang = ['23.3', '24.0']
# send arbitrary options to the plugin, passed as:
# MISE_TOOL_OPTS__VENV=.venv
python = {version= '3.10', virtualenv='.venv'}
#+end_src
And then, in the terminal, grab the version of a tool you need, like:
#+begin_src sh
mise install python
#+end_src
Next, each project defines their own =.mise.toml= like:
#+begin_src toml :tangle ~/other/python-xp/ .mise.toml
[tools]
python = '3.10'
#+end_src
And it seemlessly works:
#+begin_example
$ cd ~/other/python-xp/
$ python --version
Python 3.10.14
#+end_example
2021-11-09 00:02:13 +00:00
** direnv
Farm off commands into /virtual environments/ :
2024-04-30 00:30:45 +00:00
#+begin_src emacs-lisp :tangle no
2021-11-18 16:17:20 +00:00
(use-package direnv
:init
2023-11-06 17:36:06 +00:00
(setq direnv-always-show-summary t
2022-02-26 01:12:18 +00:00
direnv-show-paths-in-summary t)
2023-11-19 04:58:59 +00:00
(if (file-exists-p "/opt/homebrew")
2023-11-06 17:36:06 +00:00
(setq direnv--executable "/opt/homebrew/bin/direnv")
(setq direnv--executable "/usr/local/bin/direnv"))
2021-11-18 16:17:20 +00:00
:config
(direnv-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:09:43 +00:00
** Spell Checking Comments
2022-06-16 18:17:54 +00:00
The [[https://www.emacswiki.org/emacs/FlySpell#h5o-2 ][flyspell-prog-mode ]] checks for misspellings in comments.
2021-12-29 19:09:43 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-10-26 04:38:48 +00:00
(use-package flyspell
:hook (prog-mode . flyspell-prog-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
** Flycheck
2022-06-16 18:17:54 +00:00
Why use [[https://www.flycheck.org/ ][flycheck ]] over the built-in =flymake= ? Speed used to be the advantage, but I’ m now pushing much of this to LSP, so speed is less of an issue. What about when I am not using LSP? Also, since I’ ve hooked grammar checkers, I need this with global keybindings.
2022-02-26 01:12:18 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-26 01:12:18 +00:00
(use-package flycheck
2022-04-09 16:07:47 +00:00
:init
(setq next-error-message-highlight t)
2022-02-26 01:12:18 +00:00
:bind (:map flycheck-error-list-mode-map
("C-n" . 'flycheck-error-list-next-error)
("C-p" . 'flycheck-error-list-previous-error)
("j" . 'flycheck-error-list-next-error)
("k" . 'flycheck-error-list-previous-error))
:config
2022-11-03 16:28:29 +00:00
(defun flycheck-enable-checker ()
"Not sure why flycheck disables working checkers."
(interactive)
2022-12-03 01:06:31 +00:00
(let (( current-prefix-arg '(4))) ; C-u
(call-interactively 'flycheck-disable-checker)))
2022-11-03 16:28:29 +00:00
2022-02-26 01:12:18 +00:00
(flymake-mode -1)
(global-flycheck-mode)
(ha-leader "t c" 'flycheck-mode)
2022-03-09 06:01:19 +00:00
(ha-leader
2022-02-26 01:12:18 +00:00
">" '("next problem" . flycheck-next-error)
"<" '("previous problem" . flycheck-previous-error)
2022-11-03 16:28:29 +00:00
"e" '(:ignore t :which-key "errors")
2022-11-04 05:16:16 +00:00
"e n" '(flycheck-next-error :repeat t :wk "next")
"e N" '(flycheck-next-error :repeat t :wk "next")
"e p" '(flycheck-previous-error :repeat t :wk "previous")
"e P" '(flycheck-previous-error :repeat t :wk "previous")
2022-11-03 16:28:29 +00:00
"e b" '("error buffer" . flycheck-buffer)
"e c" '("clear" . flycheck-clear)
"e l" '("list all" . flycheck-list-errors)
"e g" '("goto error" . counsel-flycheck)
"e y" '("copy errors" . flycheck-copy-errors-as-kill)
"e s" '("select checker" . flycheck-select-checker)
"e ?" '("describe checker" . flycheck-describe-checker)
"e h" '("display error" . flycheck-display-error-at-point)
"e e" '("explain error" . flycheck-explain-error-at-point)
"e H" '("help" . flycheck-info)
"e i" '("manual" . flycheck-manual)
"e V" '("verify-setup" . flycheck-verify-setup)
"e v" '("version" . flycheck-verify-checker)
"e E" '("enable checker" . flycheck-enable-checker)
"e x" '("disable checker" . flycheck-disable-checker)
"e t" '("toggle flycheck" . flycheck-mode)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-23 18:58:49 +00:00
** Documentation
2022-06-16 18:17:54 +00:00
I’ m interested in using [[https://devdocs.io/ ][devdocs ]] instead, which is similar, but keeps it all /inside/ Emacs (and works on my Linux system). Two Emacs projects compete for this position. The Emacs [[https://github.com/astoff/devdocs.el ][devdocs ]] project is active, and seems to work well. Its advantage is a special mode for moving around the documentation.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-23 18:58:49 +00:00
(use-package devdocs
2023-06-15 22:44:57 +00:00
:general (:states 'normal
2024-03-22 20:43:37 +00:00
"gD" '("devdocs" . devdocs-lookup))
2022-02-23 18:58:49 +00:00
2022-05-16 20:34:05 +00:00
:config
2024-03-22 20:43:37 +00:00
(major-mode-hydra-define emacs-lisp-mode nil
("Dev Docs"
(("e" eldoc "eldoc")
("d" devdocs-lookup "open")
("p" devdocs-peruse "peruse")
("i" devdocs-install "install")
("u" devdocs-update-all "update")
("x" devdocs-delete "uninstall")
("s" devdocs-search "search")))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-23 18:58:49 +00:00
The [[https://github.com/blahgeek/emacs-devdocs-browser ][devdocs-browser ]] project acts similar, but with slightly different command names. Its advantage is that it allows for downloading docs and having it available offline, in fact, you can’ t search for a function, until you download its pack. This is slightly faster because of this.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no
2022-02-23 18:58:49 +00:00
(use-package devdocs-browser
2022-05-16 20:34:05 +00:00
:general (:states 'normal "gD" 'devdocs-browser-open)
2022-02-23 18:58:49 +00:00
2022-05-16 20:34:05 +00:00
:config
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
2022-02-23 18:58:49 +00:00
"d" '(:ignore t :which-key "docs")
"d d" '("open" . devdocs-browser-open)
"d D" '("open in" . devdocs-browser-open-in)
"d l" '("list" . devdocs-browser-list-docs)
"d u" '("update" . devdocs-browser-update-docs)
"d i" '("install" . devdocs-browser-install-doc)
"d x" '("uninstall" . devdocs-browser-uninstall-doc)
"d U" '("upgrade" . devdocs-browser-upgrade-doc)
"d o" '("download" . devdocs-browser-download-offline-data)
"d O" '("remove download" . devdocs-browser-remove-offline-data)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-20 03:35:30 +00:00
** Code Folding
2022-06-18 00:25:47 +00:00
While Emacs has options for viewing and moving around code, sometimes, we could /collapse/ all functions, and then start to expand them one at a time. For this, we could enable the built-in [[https://www.emacswiki.org/emacs/HideShow ][hide-show feature ]]:
#+begin_src emacs-lisp :tangle no
2022-05-20 03:35:30 +00:00
(use-package hide-show
:straight (:type built-in)
:init
(setq hs-hide-comments t
hs-hide-initial-comment-block t
hs-isearch-open t)
:hook (prog-mode . hs-minor-mode))
2022-06-18 00:25:47 +00:00
#+end_src
Note that =hide-show= doesn’ t work with complex YAML files. The [[https://github.com/gregsexton/origami.el ][origami ]] mode works better /out-of-the-box/ , as it works with Python and Lisp, but falls back to indents as the format, which works well.
#+begin_src emacs-lisp
2022-05-20 03:35:30 +00:00
(use-package origami
:init
(setq origami-fold-replacement "⤵")
:hook (prog-mode . origami-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-20 03:35:30 +00:00
To take advantage of this, type:
- ~z m~ :: To collapse everything
- ~z r~ :: To open everything
- ~z o~ :: To open a particular section
- ~z c~ :: To collapse a /section/ (like a function)
- ~z a~ :: Toggles open to close
Note: Yes, we could use [[https://github.com/mrkkrp/vimish-fold ][vimish-fold ]] (and its cousin, [[https://github.com/alexmurray/evil-vimish-fold ][evil-vimish-fold ]]) and we’ ll see if I need those.
2023-03-18 02:16:58 +00:00
** Smart Parenthesis
We need to make sure we keep the [[https://github.com/Fuco1/smartparens ][smartparens ]] project always in /strict mode/ , because who wants to worry about paren-matching:
#+begin_src emacs-lisp
(use-package smartparens
:custom
(smartparens-global-strict-mode t)
:config
(sp-with-modes sp-lisp-modes
;; disable ', as it's the quote character:
(sp-local-pair "'" nil :actions nil))
(sp-with-modes (-difference sp-lisp-modes sp-clojure-modes)
;; use the pseudo-quote inside strings where it serve as hyperlink.
(sp-local-pair "`" "'"
:when '(sp-in-string-p
sp-in-comment-p)
:skip-match (lambda (ms _mb _me)
(cond
((equal ms "'") (not (sp-point-in-string-or-comment)))
(t (not (sp-point-in-string-or-comment)))))))
:hook
(prog-mode . smartparens-strict-mode))
#+end_src
** Navigation
*** Move by Functions
The =mark-paragraph= and =downcase-word= isn’ t very useful in a programming context, and makes more sense to use them to jump around function-by-function:
#+begin_src emacs-lisp
2024-02-09 20:05:13 +00:00
(global-set-key (kbd "M-h") 'beginning-of-defun)
(global-set-key (kbd "M-l") 'beginning-of-next-defun)
(when (fboundp 'evil-define-key)
(evil-define-key '(normal insert emacs) prog-mode-map
(kbd "M-h") 'beginning-of-defun
(kbd "M-l") 'beginning-of-next-defun))
2023-03-18 02:16:58 +00:00
#+end_src
But one of those functions doesn’ t exist:
#+begin_src emacs-lisp
(defun beginning-of-next-defun (count)
"Move to the beginning of the following function."
(interactive "P")
(end-of-defun count)
(end-of-defun)
(beginning-of-defun))
2023-04-07 04:58:41 +00:00
#+end_src
*** Tree Sitter
2023-09-08 17:58:17 +00:00
I’ m curious about the new [[https://emacs-tree-sitter.github.io/ ][Tree Sitter feature ]] now [[https://lists.gnu.org/archive/html/emacs-devel/2022-11/msg01443.html ][built into Emacs 29 ]]. After following along with Mickey Petersen’ s [[https://www.masteringemacs.org/article/how-to-get-started-tree-sitter ][Getting Started with Tree Sitter ]] guide, I’ ve concluded I /currently/ don’ t need this feature. I’ m leaving the code here, but adding a =:tangle no= to all the blocks until I’ m ready to re-investigate.
2023-08-11 23:39:15 +00:00
**** Operating System Part
2023-04-07 04:58:41 +00:00
Install the binary for the [[https://tree-sitter.github.io/ ][tree-sitter project ]]. For instance:
#+begin_src sh
2023-05-03 04:55:22 +00:00
brew install tree-sitter npm # Since most support packages need that too.
2023-04-07 04:58:41 +00:00
#+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!
2023-05-03 04:55:22 +00:00
Next, using the =tree-sitter= command line tool, create the [[/Users/howard.abrams/Library/Application Support/tree-sitter/config.json ][config.json ]] file:
2023-04-07 04:58:41 +00:00
#+begin_src sh
tree-sitter init-config
#+end_src
2023-05-03 04:55:22 +00:00
2023-04-07 04:58:41 +00:00
Normally, you would need to add all the projects to directory clones in =~/src= , e.g.
#+begin_src sh :dir ~/src
2023-06-01 00:05:54 +00:00
while read REPO
do
LOCATION=~/src/ $(basename ${REPO})
if [ ! -d ${LOCATION} ]
then
git clone ${REPO} ${LOCATION}
fi
cd ${LOCATION}
git pull origin
npm install
done <<EOL
https://github.com/tree-sitter/tree-sitter-css
https://github.com/tree-sitter/tree-sitter-json
https://github.com/tree-sitter/tree-sitter-python
https://github.com/tree-sitter/tree-sitter-bash
https://github.com/tree-sitter/tree-sitter-ruby
https://github.com/camdencheek/tree-sitter-dockerfile
https://github.com/alemuller/tree-sitter-make
https://github.com/ikatyang/tree-sitter-yaml
https://github.com/Wilfred/tree-sitter-elisp
EOL
#+end_src
2023-08-11 23:39:15 +00:00
Seems that Docker is a bit of an odd-ball:
2023-05-03 04:55:22 +00:00
#+begin_src sh
2023-08-11 23:39:15 +00:00
mkdir -p ~/src
git -C ~/src clone https:/ /github.com/camdencheek/tree-sitter-dockerfile
make -C ~/src/tree-sitter-dockerfile && \
make -C ~/src/tree-sitter-dockerfile install
if [[ $(uname -n) = "Darwin" ]]
then
cp ~/src/tree-sitter-dockerfile/libtree-sitter-dockerfile.dylib \
~/.emacs.d/tree-sitter
else
cp ~/src/tree-sitter-dockerfile/libtree-sitter-dockerfile.so \
~/.emacs.d/tree-sitter
fi
#+end_src
In most cases,the =npm install= /usually/ works, but I may work on some sort of various process, for instance:
#+begin_src shell
2023-06-01 00:05:54 +00:00
for TSS in ~/src/tree-sitter-*
do
cd $TSS
2023-08-11 23:39:15 +00:00
NAME=$(pwd | sed 's/.*-/ /')
git pull origin
2023-06-01 00:05:54 +00:00
npm install || cargo build || make install # Various build processes!?
2023-08-11 23:39:15 +00:00
2023-09-08 17:58:17 +00:00
echo "Do we need to copy the library into ~/.emacs.d/tree-sitter/ $NAME ?"
2023-08-11 23:39:15 +00:00
# if [ "$(uname -o)" = "Darwin" ]
# then
# cp libtree-sitter-$NAME.dylib ~/.emacs.d/tree-sitter
# else
# cp libtree-sitter-$NAME.so ~/.emacs.d/tree-sitter
# fi
2023-06-01 00:05:54 +00:00
done
2023-04-07 04:58:41 +00:00
#+end_src
2023-08-11 23:39:15 +00:00
At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>=
**** Emacs Part
2023-04-07 04:58:41 +00:00
However, Emacs already has the ability to download and install grammars, so following instructions from Mickey Petersen’ s essay on [[https://www.masteringemacs.org/article/combobulate-structured-movement-editing-treesitter ][using Tree-sitter with Combobulate ]]:
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
2023-04-07 04:58:41 +00:00
(when (string-search "TREE_SITTER" system-configuration-features)
(use-package treesit
:straight (:type built-in)
:preface
2023-08-11 23:39:15 +00:00
(setq treesit-language-source-alist
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
;; (c "https://github.com/tree-sitter/tree-sitter-c/ " "master" "src")
(clojure "https://github.com/sogaiu/tree-sitter-clojure" "master" "src")
;; (cpp "https://github.com/tree-sitter/tree-sitter-cpp/ " "master" "src")
;; (cmake "https://github.com/uyha/tree-sitter-cmake")
(css "https://github.com/tree-sitter/tree-sitter-css")
(dockerfile "https://github.com/camdencheek/tree-sitter-dockerfile" "main" "src")
;; From my private cloned repository:
;; (dockerfile "file:///opt/src/github/tree-sitter-dockerfile" "main" "src")
;; The Emacs Lisp Tree Sitter doesn't work with Emacs (go figure):
;; (elisp "https://github.com/Wilfred/tree-sitter-elisp")
;; (elixir "https://github.com/elixir-lang/tree-sitter-elixir" "main" "src")
;; (erlang "https://github.com/WhatsApp/tree-sitter-erlang" "main" "src")
(go "https://github.com/tree-sitter/tree-sitter-go")
;; (haskell "https://github.com/tree-sitter/tree-sitter-haskell" "master" "src")
(html "https://github.com/tree-sitter/tree-sitter-html")
;; (java "https://github.com/tree-sitter/tree-sitter-java" "master" "src")
;; (javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
(json "https://github.com/tree-sitter/tree-sitter-json")
;; (julia "https://github.com/tree-sitter/tree-sitter-julia" "master" "src")
;; (lua "https://github.com/MunifTanjim/tree-sitter-lua" "main" "src")
(make "https://github.com/alemuller/tree-sitter-make")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
;; (meson "https://github.com/Decodetalkers/tree-sitter-meson" "master" "src")
(python "https://github.com/tree-sitter/tree-sitter-python")
(ruby "https://github.com/tree-sitter/tree-sitter-ruby" "master" "src")
(rust "https://github.com/tree-sitter/tree-sitter-rust" "master" "src")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
;; (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
;; (typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml")))
2023-04-07 04:58:41 +00:00
(defun mp-setup-install-grammars ()
"Install Tree-sitter grammars if they are absent."
(interactive)
2023-08-11 23:39:15 +00:00
(sit-for 30)
(mapc #'treesit-install-language-grammar (mapcar #'car treesit-language-source-alist)))
;; Optional, but Mickey recommends. Tree-sitter enabled major
;; modes are distinct from their ordinary counterparts, however,
;; the `tree-sitter-mode' can't be enabled if we use this
;; feature.
;;
;; You can remap major modes with `major-mode-remap-alist'. Note
;; this does *not* extend to hooks! Make sure you migrate them also
;; (dolist (mapping '((bash-mode . bash-ts-mode)
;; (sh-mode . bash-ts-mode)
;; (css-mode . css-ts-mode)
;; (dockerfile-mode . dockerfile-ts-mode)
;; (json-mode . json-ts-mode)
;; (makefile-mode . makefile-ts-mode)
;; (python-mode . python-ts-mode)
;; (ruby-mode . ruby-ts-mode)
;; (yaml-mode . yaml-ts-mode)))
;; (add-to-list 'major-mode-remap-alist mapping))
;; Can we (do we need to) update this list?
;; (add-to-list 'tree-sitter-major-mode-language-alist mapping))
2023-04-07 04:58:41 +00:00
:config
(mp-setup-install-grammars)))
#+end_src
2023-08-11 23:39:15 +00:00
And enable the languages:
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
(when (treesit-available-p)
2023-08-11 23:39:15 +00:00
(use-package tree-sitter-langs
2023-09-08 17:58:17 +00:00
:after treesit
2023-08-11 23:39:15 +00:00
:config
(global-tree-sitter-mode)))
#+end_src
2023-04-07 04:58:41 +00:00
*** Combobulate
I like [[file:ha-programming-elisp.org::*Clever Parenthesis ][Clever Parenthesis ]], but can we extend that to other languages generally? After reading Mickey Petersen’ s essay, [[https://www.masteringemacs.org/article/combobulate-structured-movement-editing-treesitter ][Combobulate project ]], I decided to try out his [[https://github.com/mickeynp/combobulate ][combobulate package ]]. Of course, this can only work with the underlying tooling supplied by the [[https://emacs-tree-sitter.github.io/ ][Tree Sitter ]] →
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
(when (treesit-available-p)
2023-04-07 04:58:41 +00:00
(use-package combobulate
:straight (:host github :repo "mickeynp/combobulate")
:after treesit
2023-08-11 23:39:15 +00:00
;; :hook ((css-ts-mode . combobulate-mode)
;; (json-ts-mode . combobulate-mode)
;; (python-ts-mode . combobulate-mode)
;; (yaml-ts-mode . combobulate-mode))
))
2023-04-07 04:58:41 +00:00
#+end_src
I can create a /helper function/ to allow me to jump to various types of—well, /types/ :
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
(when (treesit-available-p)
2023-04-07 04:58:41 +00:00
(use-package combobulate
2023-09-08 17:58:17 +00:00
:config
(defun ha-comb-jump (&rest tree-sitter-types)
"Use `avy' to jump to a particular type of element.6 "
(lexical-let ((types tree-sitter-types))
(lambda ()
(interactive)
(with-navigation-nodes (:nodes types)
(combobulate-avy-jump)))))))
2023-04-07 04:58:41 +00:00
#+end_src
Now, I can create an /interface/ of keystrokes to jump around like a boss:
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
(when (treesit-available-p)
2023-04-07 04:58:41 +00:00
(use-package combobulate
:general
2023-04-19 15:47:47 +00:00
(:states 'visual :keymaps 'combobulate-key-map
"o" '("mark node" . combobulate-mark-node-dwim)) ; Mark symbol since "o" doesn't do anything
2023-04-07 04:58:41 +00:00
(:states 'normal :keymaps 'combobulate-key-map
"g J" '("avy jump" . combobulate-avy)
"[ [" '("prev node" . combobulate-navigate-logical-previous)
"] ]" '("next node" . combobulate-navigate-logical-next)
"[ f" '("prev defun" . combobulate-navigate-beginning-of-defun)
"] f" '("next defun" . combobulate-navigate-end-of-defun)
"[ m" '("drag back" . combobulate-drag-up)
"] m" '("drag forward" . combobulate-drag-down)
"[ r" '("raise" . combobulate-vanish-node)
"g j" '(:ignore t :which-key "combobulate jump")
"g j j" '("all" . combobulate-avy-jump)
"g j s" `("strings" . ,(ha-comb-jump "string"))
"g j c" `("comments" . ,(ha-comb-jump "comment"))
"g j i" `("conditionals" . ,(ha-comb-jump "conditional_expression" "if_statement"
"if_clause" "else_clause" "elif_clause" ))
"g j l" `("loops" . ,(ha-comb-jump "for_statement" "for_in_clause" "while_statement"
"list_comprehension" "dictionary_comprehension" "set_comprehension"))
"g j f" '("functions" . combobulate-avy-jump-defun))))
#+end_src
Mickey’ s interface is the [[help:combobulate ][combobulate ]] function (or ~C-c o o~ ), but mine is more /evil/ .
*** Evil Text Object from Tree Sitter
With Emacs version 29, we get a better approach to parsing languages, and this means that our [[https://github.com/nvim-treesitter/nvim-treesitter-textobjects#built-in-textobjects ][text objects ]] can be better too with the [[https://github.com/meain/evil-textobj-tree-sitter ][evil-textobj-tree-sitter project ]]:
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no
2024-02-09 20:05:13 +00:00
(when (and (treesit-available-p) (fboundp 'evil-define-text-object))
2023-04-07 04:58:41 +00:00
(use-package evil-textobj-tree-sitter
:config
;; We need to bind keys to the text objects found at:
;; https://github.com/nvim-treesitter/nvim-treesitter-textobjects#built-in-textobjects
;; bind `function.outer`(entire function block) to `f` for use in things like `vaf`, `yaf`
(define-key evil-outer-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.outer"))
;; bind `function.inner`(function block without name and args) to `f` for use in things like `vif`, `yif`
(define-key evil-inner-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.inner"))
(define-key evil-outer-text-objects-map "c" (evil-textobj-tree-sitter-get-textobj "comment.outer"))
(define-key evil-inner-text-objects-map "c" (evil-textobj-tree-sitter-get-textobj "comment.inner"))
2023-08-11 23:39:15 +00:00
(define-key evil-outer-text-objects-map "u" (evil-textobj-tree-sitter-get-textobj "conditional.outer"))
(define-key evil-inner-text-objects-map "u" (evil-textobj-tree-sitter-get-textobj "conditional.inner"))
2023-04-07 04:58:41 +00:00
(define-key evil-outer-text-objects-map "b" (evil-textobj-tree-sitter-get-textobj "loop.outer"))
(define-key evil-inner-text-objects-map "b" (evil-textobj-tree-sitter-get-textobj "loop.inner"))))
#+end_src
Seems the macro, =evil-textobj-tree-sitter-get-textobj= has a bug, so the following—which would have been easier to write—doesn’ t work:
2023-09-08 17:58:17 +00:00
#+begin_src emacs-lisp :tangle no :tangle no
2023-04-07 04:58:41 +00:00
(dolist (combo '(("f" "function.outer" "function.inner")
("b" "loop.outer" "loop.inner")
;; ...
("c" "comment.outer" "comment.inner")))
(destructuring-bind (key outer inner) combo
;; bind an outer (e.g. entire function block) for use in things like `vaf`, `yaf` combo
(define-key evil-outer-text-objects-map key (evil-textobj-tree-sitter-get-textobj outer))
;; bind an inner (e.g. function block without name and args) for use in things like `vif`, `yif`
(define-key evil-inner-text-objects-map key (evil-textobj-tree-sitter-get-textobj inner))))
2023-03-18 02:16:58 +00:00
#+end_src
2023-03-20 19:07:32 +00:00
*** dumb-jump
2022-10-26 04:38:48 +00:00
Once upon a time, we use to create a =TAGS= file that contained the database for navigating code bases, but with new faster versions of grep, e.g. [[https://beyondgrep.com ][ack ]], [[https://github.com/ggreer/the_silver_searcher ][ag ]] (aka, the Silver Searcher), [[https://github.com/Genivia/ugrep ][ugrep ]] and [[https://github.com/BurntSushi/ripgrep ][ripgrep ]], we should be able to use them. but I want to:
2022-09-01 04:47:18 +00:00
- Be in a function, and see its callers. For this, the [[help:rg-dwim ][rg-dwim ]] function is my bread-and-butter.
- Be on a function, and jump to the definition. For this, I use [[https://github.com/jacktasia/dumb-jump ][dumb-jump ]], which uses the above utilities.
#+begin_src emacs-lisp
(use-package dumb-jump
:config
2023-03-20 19:07:32 +00:00
(setq dumb-jump-prefer-searcher 'rg
xref-history-storage #'xref-window-local-history
xref-show-definitions-function #'xref-show-definitions-completing-read)
2022-10-26 04:38:48 +00:00
2022-09-01 05:33:42 +00:00
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
2022-10-26 04:38:48 +00:00
;; Remove this now that https://github.com/jacktasia/dumb-jump/issues/338
;; (defun evil-set-jump-args (&rest ns) (evil-set-jump))
;; (advice-add 'dumb-jump-goto-file-line :before #'evil-set-jump-args)
2022-09-01 04:47:18 +00:00
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
2022-09-01 05:33:42 +00:00
"s" '(:ignore t :which-key "search")
2022-10-26 04:38:48 +00:00
"s s" '("search" . xref-find-apropos)
"s d" '("definitions" . xref-find-definitions)
2022-09-01 05:33:42 +00:00
"s o" '("other window" . xref-find-definitions-other-window)
2022-10-26 04:38:48 +00:00
"s r" '("references" . xref-find-references)
"s b" '("back" . xref-go-back)
"s f" '("forward" . xref-go-forward))
2022-09-01 05:33:42 +00:00
2023-05-01 18:49:33 +00:00
:general
(:states 'normal
"g ." '("find def" . xref-find-definitions)
"g >" '("find def o/win" . xref-find-definitions-other-window)
"g ," '("def go back" . xref-go-back)
"g <" '("def go forward" . xref-go-forward)
"g /" '("find refs" . xref-find-references)
"g ?" '("find/rep refs" . xref-find-references-and-replace)
"g h" '("find apropos" . xref-find-apropos)
"g b" '("def go back" . xref-go-back)))
2022-09-01 05:33:42 +00:00
#+end_src
2022-10-26 04:38:48 +00:00
I have two different /jumping/ systems, the [[info:emacs#Xref ][Xref interface ]] and Evil’ s. While comparable goals, they are behave different. Let’ s compare evil keybindings:
| ~M-.~ | ~g .~ | [[help:xref-find-definitions][xref-find-definitions]] (also ~g d~ for [[help:evil-goto-definition][evil-goto-definition]])† |
| | ~g >~ | =xref-find-definitions-other-window= |
| ~M-,~ | ~g ,~ | [[help:xref-go-back][xref-go-back]] (see [[help:xref-pop-marker-stack][xref-pop-marker-stack]]) |
| ~C-M-,~ | ~g <~ | [[help:xref-go-forward][xref-go-forward]] (kinda like =xref-find-definitions=) |
| ~M-?~ | ~g /~ | [[help:xref-find-references][xref-find-references]] to go from definition to code calls‡ |
| | ~g ?~ | [[help:xref-find-references-and-replace][xref-find-references-and-replace]] could be more accurate than [[*iEdit][iEdit]]. |
| ~C-M-.~ | ~g h~ | [[help:xref-find-apropos][xref-find-apropos]] … doesn’ t work well without LSP |
| ~C-TAB~ | | perform completion around point (also ~M-TAB~), see [[file:ha-config.org::*Auto Completion][Auto Completion]]. |
† Prefix to prompt for the term \
‡ If it finds more than one definition, Emacs displays the [[info:emacs#Xref Commands ][*xref* buffer ]], allowing you to select the definition.
2021-11-18 16:17:20 +00:00
** Language Server Protocol (LSP) Integration
2022-10-26 04:38:48 +00:00
The [[https://microsoft.github.io/language-server-protocol/ ][LSP ]] is a way to connect /editors/ (like Emacs) to /languages/ (like Lisp)… wait, no. While originally designed for VS Code and probably Python, we can abstract away [[https://github.com/davidhalter/jedi ][Jedi ]] and the [[http://tkf.github.io/emacs-jedi/latest/ ][Emacs integration to Jedi ]] (and duplicate everything for Ruby, and Clojure, and…).
2021-11-18 16:17:20 +00:00
2022-08-10 04:29:41 +00:00
Emacs has two LSP projects, and while I have used [[LSP Mode ]], but since I don’ t have heavy IDE requirements, I am finding that [[eglot ]] to be simpler.
2023-01-10 03:52:49 +00:00
*** LSP
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2023-01-10 03:52:49 +00:00
(use-package lsp-mode
:commands (lsp lsp-deferred)
2022-10-26 04:38:48 +00:00
:init
2023-01-10 03:52:49 +00:00
;; Let's make lsp-doctor happy with these settings:
(setq gc-cons-threshold (* 100 1024 1024)
read-process-output-max (* 1024 1024)
company-idle-delay 0.0 ; Are thing fast enough to do this?
lsp-keymap-prefix "s-m")
2022-10-26 04:38:48 +00:00
2022-02-26 01:12:18 +00:00
:config
2023-01-10 03:52:49 +00:00
(global-set-key (kbd "s-m") 'lsp)
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
2023-01-10 03:52:49 +00:00
"w" '(:ignore t :which-key "lsp")
"l" '(:ignore t :which-key "lsp")
"ws" '("start" . lsp))
2022-10-26 04:38:48 +00:00
2023-01-10 03:52:49 +00:00
;; The following leader-like keys, are only available when I have
;; started LSP, and is an alternate to Command-m:
2022-10-26 04:38:48 +00:00
:general
2023-01-10 03:52:49 +00:00
(:states 'normal :keymaps 'lsp-mode-map
2023-03-20 21:01:40 +00:00
", w r" '("restart" . lsp-reconnect)
", w b" '("events" . lsp-events-buffer)
", w e" '("errors" . lsp-stderr-buffer)
", w q" '("quit" . lsp-shutdown)
", w Q" '("quit all" . lsp-shutdown-all)
", l r" '("rename" . lsp-rename)
", l f" '("format" . lsp-format)
", l a" '("actions" . lsp-code-actions)
", l i" '("imports" . lsp-code-action-organize-imports)
", l d" '("doc" . lsp-lookup-documentation))
2023-01-10 03:52:49 +00:00
:hook ((lsp-mode . lsp-enable-which-key-integration)))
#+end_src
2023-03-20 21:01:40 +00:00
I will want to start adding commands under my =,= mode-specific key sequence leader, but in the meantime, all LSP-related keybindings are available under ~⌘-m~ . See [[https://emacs-lsp.github.io/lsp-mode/page/keybindings/ ][this page ]] for the default keybindings.
2023-01-10 03:52:49 +00:00
*** UI
The [[https://github.com/emacs-lsp/lsp-ui ][lsp-ui ]] project offers much of the display and interface to LSP. Seems to make the screen cluttered.
#+begin_src emacs-lisp
(use-package lsp-ui
:commands lsp-ui-mode
:config
(setq lsp-ui-sideline-ignore-duplicate t
lsp-ui-sideline-show-hover t
lsp-ui-sideline-show-diagnostics t)
:hook (lsp-mode . lsp-ui-mode))
#+end_src
*** Treemacs
#+begin_src emacs-lisp
(use-package lsp-treemacs
:commands lsp-treemacs-errors-list
:bind
(:map prog-mode-map
("s-)" . treemacs))
(:map treemacs-mode-map
("s-)" . treemacs))
:config
(lsp-treemacs-sync-mode 1))
#+end_src
*** Company Completion
The [[https://github.com/tigersoldier/company-lsp ][company-lsp ]] offers a [[http://company-mode.github.io/ ][company ]] completion backend for [[https://github.com/emacs-lsp/lsp-mode ][lsp-mode ]]:
2022-10-26 04:38:48 +00:00
2023-01-10 03:52:49 +00:00
#+begin_src emacs-lisp :tangle no
(use-package company-lsp
:config
(push 'company-lsp company-backends))
2022-10-26 04:38:48 +00:00
#+end_src
2023-01-10 03:52:49 +00:00
To options that might be interesting:
- =company-lsp-async= : When set to non-nil, fetch completion candidates asynchronously.
- =company-lsp-enable-snippet= : Set it to non-nil if you want to enable snippet expansion on completion. Set it to nil to disable this feature.
2022-10-26 04:38:48 +00:00
2023-01-10 03:52:49 +00:00
*** iMenu
The [[https://github.com/emacs-lsp/lsp-ui/blob/master/lsp-ui-imenu.el ][lsp-imenu ]] project offers a =lsp-ui-imenu= function for jumping to functions:
2022-10-26 04:38:48 +00:00
2023-01-10 03:52:49 +00:00
#+begin_src emacs-lisp :tangle no
(use-package lsp-ui-imenu
:straight nil
:after lsp-ui
:config
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
2023-01-10 03:52:49 +00:00
"g" '(:ignore t :which-key "goto")
"g m" '("imenu" . lsp-ui-imenu))
(add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
2022-08-10 04:29:41 +00:00
#+end_src
2022-09-01 04:47:46 +00:00
*** Display Configuration
Using the [[https://github.com/seagle0128/doom-modeline ][Doom Modeline ]] to add notifications:
#+begin_src emacs-lisp
(use-package doom-modeline
:config
(setq doom-modeline-lsp t
doom-modeline-env-version t))
#+end_src
2021-11-09 00:02:13 +00:00
** Function Call Notifications
As I've mentioned [[http://www.howardism.org/Technical/Emacs/beep-for-emacs.html ][on my website ]], I've created a [[file:~/website/Technical/Emacs/beep-for-emacs.org ][beep function ]] that notifies when long running processes complete.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-27 17:34:03 +00:00
(use-package alert
:init
(setq alert-default-style
(if (ha-running-on-macos?)
'osx-notifier
'libnotify)))
2021-11-09 16:21:08 +00:00
(use-package beep
2021-12-27 17:34:03 +00:00
:straight nil ; Already in the load-path
2023-08-29 19:00:32 +00:00
:hook (after-init . (lambda () (beep-when-finished "Emacs has started." "Eemacs has started")))
2021-12-27 17:34:03 +00:00
:config
(dolist (func '(org-publish
org-publish-all
org-publish-project
compile
shell-command))
(advice-add func :around #'beep-when-runs-too-long)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
While that code /advices/ the publishing and compile commands, I may want to add more.
** iEdit
2021-12-30 02:51:25 +00:00
While there are language-specific ways to rename variables and functions, [[https://github.com/victorhge/iedit ][iedit ]] is often sufficient.
2023-11-16 16:57:04 +00:00
#+begin_src emacs-lisp :tangle no
2022-03-03 23:19:31 +00:00
(use-package iedit
:config
(ha-leader "s e" '("iedit" . iedit-mode)))
2022-06-18 00:25:47 +00:00
#+end_src
2023-11-16 16:57:04 +00:00
While =iedit= acts a little odd with Evil, the [[https://github.com/syl20bnr/evil-iedit-state ][evil-iedit-state project ]] attempts to makes the interface more intuitive.
This creates both an =iedit= and =iedit-insert= states. Calling ~Escape~ from =iedit-insert= goes to =iedit= , and hitting it again, will go back to =normal= state.
To use, highlight a region with ~v~ , and continue to hit ~v~ until you’ ve selected the variable/symbol, and then type ~e~ . Or, highlight normally, e.g. ~v i o~ , and hit ~E~ :
#+begin_src emacs-lisp
2024-02-09 20:05:13 +00:00
(when (fboundp 'evil-mode)
(use-package evil-iedit-state
:after iedit
:general
(:states 'visual "E" '("iedit" . evil-iedit-state/iedit-mode))))
2023-11-16 16:57:04 +00:00
#+end_src
The =iedit-insert= state is pretty much /regular/ =insert= state, so the interesting keys are in =iedit= state:
- ~0~ / ~$~ :: jump to beginning/end of the “occurrence”
- ~n~ / ~N~ :: jump to next / previous occurrence
- ~I~ / ~A~ :: jump to beginning/end of occurrence and go into =iedit-insert= mode (obviously ~a~ and ~i~ do too)
- ~#~ :: highlights all the matching occurrences
- ~F~ :: restricts to the current function
2023-07-05 17:18:18 +00:00
** Case Conversion
2023-07-11 17:10:58 +00:00
The [[https://github.com/akicho8/string-inflection ][string-inflection ]] project (see [[http://sodaware.sdf.org/notes/converting-to-snake-case-in-emacs/ ][this overview ]]) converts symbol variables to /appropriate format/ for the mode. This replaces my home-brewed functions.
2023-07-05 17:18:18 +00:00
#+begin_src emacs-lisp
2023-07-11 17:10:58 +00:00
(use-package string-inflection
2023-07-05 17:18:18 +00:00
:general
(:states '(normal visual motion operator)
2023-07-11 17:10:58 +00:00
"z s" '("to snake case" . string-inflection-underscore)
"z S" '("to Snake Case" . string-inflection-upcase)
"z c" '("to camelCase" . string-inflection-lower-camelcase)
"z C" '("to CamelCase" . string-inflection-camelcase)
"z -" '("to kebab case" . string-inflection-kebab-case)
"z z" '("toggle snake/camel" . string-inflection-all-cycle)))
2023-07-05 17:18:18 +00:00
#+end_src
I would like to have this bound on the ~g~ sequence, but that is crowded.
2023-07-11 17:10:58 +00:00
Note that ~g u~ (for lower-casing stuff), and ~g U~ (for up-casing) requires /something/ , for instance ~g U i o~ upper-cases the symbol at point. These functions, however, only work with a symbol (which is the typical case).
2021-11-09 00:02:13 +00:00
** Commenting
I like =comment-dwim= (~M-;~ ), and I like =comment-box= , but I have an odd personal style that I like to codify:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-10-26 04:38:48 +00:00
(defun ha-comment-line (&optional start end)
"Comment a line or region with a block-level format.
Calls `comment-region' with START and END set to the region or
the start and end of the line."
(interactive)
(when (or (null start) (not (region-active-p)))
(setq start (line-beginning-position))
(setq end (line-end-position)))
(save-excursion
(narrow-to-region start end)
(upcase-region start end)
(goto-char (point-min))
(insert "------------------------------------------------------------------------\n")
(goto-char (point-max))
(insert "\n------------------------------------------------------------------------")
(comment-region (point-min) (point-max))
(widen)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
And a keybinding:
2024-03-22 20:43:37 +00:00
#+begin_src emacs-lisp :tangle no
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
"c" '(:ignore t :which-key "comment")
"c l" '("comment line" . ha-comment-line))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
** Evaluation
2022-05-11 17:53:20 +00:00
While I like [[help:eval-print-last-sexp ][eval-print-last-sexp ]], I would like a bit of formatting in order to /keep the results/ in the file.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-11 17:53:20 +00:00
(defun ha-eval-print-last-sexp (&optional internal-arg)
"Evaluate the expression located before the point.
2022-10-26 04:38:48 +00:00
Insert results back into the buffer at the end of the line after
a comment."
2022-05-11 17:53:20 +00:00
(interactive)
(save-excursion
(eval-print-last-sexp internal-arg))
(end-of-line)
(insert " ")
(insert comment-start)
(insert "⟹ ")
(dotimes (i 2)
(next-line)
(join-line)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-11 17:53:20 +00:00
2021-11-09 00:02:13 +00:00
Typical keybindings for all programming modes:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2023-05-01 18:49:33 +00:00
(ha-local-leader :keymaps 'prog-mode-map
2021-11-09 00:02:13 +00:00
"e" '(:ignore t :which-key "eval")
"e ;" '("expression" . eval-expression)
"e b" '("buffer" . eval-buffer)
"e f" '("function" . eval-defun)
"e r" '("region" . eval-region)
2023-08-14 18:45:54 +00:00
"e e" '("eval exp" . eval-last-sexp)
2022-05-11 17:53:20 +00:00
"e p" '("print s-exp" . ha-eval-print-last-sexp))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
** Ligatures
2022-04-19 04:55:55 +00:00
The idea of using math symbols for a programming languages keywords is /cute/ , but can be confusing, so I use it sparingly:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-11 19:39:14 +00:00
(defun ha-prettify-prog ()
2022-05-11 17:53:20 +00:00
"Extends the `prettify-symbols-alist' for programming."
2022-05-11 19:39:14 +00:00
(mapc (lambda (pair) (push pair prettify-symbols-alist))
2022-05-11 17:53:20 +00:00
'(("lambda" . "𝝀")
(">=" . "≥")
("<=" . "≤")
("!=" . "≠")))
(prettify-symbols-mode))
(add-hook 'prog-mode-hook 'ha-prettify-prog)
2022-06-18 00:25:47 +00:00
#+end_src
2023-03-24 17:58:22 +00:00
2022-10-26 04:38:48 +00:00
Hopefully I can follow [[https://www.masteringemacs.org/article/unicode-ligatures-color-emoji ][Mickey Petersen's essay ]] on getting full ligatures working, but right now, they don’ t work on the Mac, and that is my current workhorse.
2023-03-24 17:58:22 +00:00
#+begin_src emacs-lisp
(use-package ligature
:config
;; Enable the "www" ligature in every possible major mode
(ligature-set-ligatures 't '("www"))
;; Enable traditional ligature support in eww-mode, if the
;; `variable-pitch' face supports it
(ligature-set-ligatures '(org-mode eww-mode) '("ff" "fi" "ffi"))
(ligature-set-ligatures '(html-mode nxml-mode web-mode)
'("<!--" "-- >" "</ >" "</" "/ >" "://"))
;; Create a new ligature:
(ligature-set-ligatures 'markdown-mode '(("=" (rx (+ "= ") (? (| ">" "<"))))
("-" (rx (+ "-")))))
;; Enable all Cascadia Code ligatures in programming modes
2023-12-20 04:13:33 +00:00
(ligature-set-ligatures
'prog-mode '("|||>" "<|||" "<== >" "<!--" "####" "~~ >" "***" "||=" "||>"
":::" "::=" "= :=" "= ==" "= =>" "= !=" "= >>" "=<<" "= /=" "!= ="
"!!." ">=>" ">>= " ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
"<~~" "<~ >" "<* >" "<||" "<| >" "<$ >" "<==" "<= >" "<=<" "<- >"
"<--" "<-<" "<<=" "<<-" "<<<" "<+ >" "</ >" "###" "#_(" "..<"
"..." "+++" "/==" "/ //" "_|_ " "www" "&&" "^=" "~~" "~@" "~= "
"~>" "~ -" "**" "* >" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
"[|" "]#" "::" ":=" ":>" ":<" "$>" "= =" "= >" "!=" "!!" ">:"
">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~ " "<*" "<|" "<:"
"<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#= " "#!"
"##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "+ +" "?:"
"?=" "?." "??" ";;" "/*" "/= " "/>" "/ /" "__" "~~" "(*" "* )"
"\\\\" "://"))
2023-03-24 17:58:22 +00:00
;; Enables ligature checks globally in all buffers. You can also do it
;; per mode with `ligature-mode'.
(global-ligature-mode t))
#+end_src
Until I can get [[https://github.com/d12frosted/homebrew-emacs-plus/issues/222 ][Harfbuzz support ]] on my Emacs-Plus build of Mac, the following work-around seems to mostly work:
#+begin_src emacs-lisp
(defun ha-mac-litagure-workaround ()
"Implement an old work-around for ligature support.
This kludge seems to only need to be set for my Mac version of
Emacs, since I can't build it with Harfuzz support."
(let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!= ]\\)")
(35 . ".\\(?:###\\|##\\|_(\\|[#(?[_ {]\\)")
(36 . ".\\(?:>\\)")
(37 . ".\\(?:\\(?:%%\\)\\|%\\)")
(38 . ".\\(?:\\(?:&&\\)\\|&\\)")
(42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\* [*/]\\)\\|[* />]\\)")
(43 . ".\\(?:\\(?:\\+\\+ \\)\\|[+>]\\)")
(45 . ".\\(?:\\(?:-[>-]\\|<<\\| >>\\)\\|[<>}~-]\\)")
(46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)")
(47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[* /=>]\\)")
(48 . ".\\(?:x[a-zA-Z]\\)")
(58 . ".\\(?:::\\|[:=]\\)")
(59 . ".\\(?:;;\\|;\\)")
(60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~ /<= >|-]\\)")
(61 . ".\\(?:\\(?:/=\\|:= \\|<<\\|=[= >]\\|>>\\)\\|[<= >~]\\)")
(62 . ".\\(?:\\(?:=>\\|>[= >-]\\)\\|[=>-]\\)")
(63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)")
(91 . ".\\(?:]\\)")
(92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)")
(94 . ".\\(?:=\\)")
(119 . ".\\(?:ww\\)")
(123 . ".\\(?:-\\)")
(124 . ".\\(?:\\(?:|[=|]\\)\\|[= >|]\\)")
(126 . ".\\(?:~>\\|~ ~\\|[>=@~ -]\\)"))))
(dolist (char-regexp alist)
(set-char-table-range composition-function-table (car char-regexp)
`([,(cdr char-regexp) 0 font-shape-gstring])))))
(unless (s-contains? "HARFBUZZ" system-configuration-features)
2023-04-01 23:23:56 +00:00
(add-hook 'prog-mode-hook #'ha-mac-litagure-workaround))
2023-03-24 17:58:22 +00:00
#+end_src
The unicode-fonts package rejigs the internal tables Emacs uses to pick better fonts for unicode codepoint ranges.
2023-12-20 04:11:35 +00:00
#+begin_src emacs-lisp :tangle no
2023-03-24 17:58:22 +00:00
(use-package unicode-fonts
:config
2023-12-20 04:11:35 +00:00
(ignore-errors
(unicode-fonts-setup)))
2023-03-24 17:58:22 +00:00
#+end_src
2022-11-04 05:17:12 +00:00
** Compiling
The [[help:compile ][compile ]] function lets me enter a command to run, or I can search the history for a previous run. What it doesn’ t give me, is a project-specific list of commands. Perhaps, for each project, I define in =.dir-locals.el= a variable, =compile-command-list= , like:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no
2022-11-04 05:17:12 +00:00
((nil . ((compile-command . "make -k ")
(compile-command-list . ("ansible-playbook playbooks/confluence_test.yml"
"ansible-playbook playbooks/refresh_inventory.yml")))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
2022-11-04 05:17:12 +00:00
To make the =compile-command-list= variable less risky, we need to declare it:
#+begin_src emacs-lisp
(defvar compile-command-list nil "A list of potential commands to give to `ha-project-compile'.")
2021-11-09 00:02:13 +00:00
2022-11-04 05:17:12 +00:00
(defun ha-make-compile-command-list-safe ()
"Add the current value of `compile-command-list' safe."
(interactive)
(add-to-list 'safe-local-variable-values `(compile-command-list . ,compile-command-list)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
2022-11-04 05:17:12 +00:00
What compile commands should I have on offer? Along with the values in =compile-command-list= (if set), I could look at files in the project’ s root and get targets from a =Makefile= , etc. We’ ll use helper functions I define later:
#+begin_src emacs-lisp
(defun ha--compile-command-list ()
"Return list of potential commands for a project."
2024-02-09 22:00:12 +00:00
(let ((default-directory (project-root (project-current))))
2022-11-04 05:17:12 +00:00
;; Make a list of ALL the things.
;; Note that `concat' returns an empty string if you give it null,
;; so we use `-concat' the dash library:
(-concat
compile-history
(ha--makefile-completions)
(ha--toxfile-completions)
(when (and (boundp 'compile-command-list) (listp compile-command-list))
compile-command-list))))
#+end_src
2021-11-09 00:02:13 +00:00
2022-11-25 07:13:09 +00:00
My replacement to [[help:compile ][compile ]] uses my new =completing-read= function:
2022-11-04 05:17:12 +00:00
#+begin_src emacs-lisp
(defun ha-project-compile (command)
"Run `compile' from a list of directory-specific commands."
(interactive (list (completing-read "Compile command: "
(ha--compile-command-list)
nil nil "" 'compile-history)))
2024-02-09 22:00:12 +00:00
(let ((default-directory (project-root (project-current))))
2022-11-25 07:13:09 +00:00
(cond
((string-match rx-compile-to-vterm command) (ha-compile-vterm command))
((string-match rx-compile-to-eshell command) (ha-compile-eshell command))
(t (compile command)))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
2022-11-25 07:13:09 +00:00
If I end a command with a =|v= , it sends the compile command to a vterm session for the project, allowing me to continue the commands:
#+begin_src emacs-lisp
(defvar rx-compile-to-vterm (rx "|" (0+ space) "v" (0+ space) line-end))
(defun ha-compile-vterm (full-command &optional project-dir)
(unless project-dir
2024-02-09 22:00:12 +00:00
(setq project-dir (project-name (project-current))))
2022-11-25 07:13:09 +00:00
;; (add-to-list 'compile-history full-command)
(let ((command (replace-regexp-in-string rx-compile-to-vterm "" full-command)))
2023-06-01 00:08:15 +00:00
(ha-ssh-send command project-dir)))
2022-11-25 07:13:09 +00:00
#+end_src
2023-01-05 05:29:39 +00:00
And what about sending the command to Eshell as well?
2022-11-25 07:13:09 +00:00
#+begin_src emacs-lisp
(defvar rx-compile-to-eshell (rx "|" (0+ space) "s" (0+ space) line-end))
2023-01-10 03:54:14 +00:00
(defun ha-compile-eshell (full-command &optional project-dir)
"Send a command to the currently running Eshell terminal.
If a terminal isn't running, it will be started, allowing follow-up
commands."
(unless project-dir
2024-02-09 22:00:12 +00:00
(setq project-dir (project-name (project-current))))
2023-01-10 03:54:14 +00:00
2023-01-05 05:29:39 +00:00
(let ((command (replace-regexp-in-string rx-compile-to-eshell "" full-command)))
2023-01-10 03:54:14 +00:00
(ha-eshell-send command project-dir)))
2022-11-25 07:13:09 +00:00
#+end_src
2022-11-04 05:17:12 +00:00
And let’ s add it to the Project leader:
#+begin_src emacs-lisp
(ha-leader "p C" 'ha-project-compile)
#+end_src
Note that =p c= (to call [[help:recompile ][recompile ]]) should still work.
Other people’ s projects:
- [[https://github.com/Olivia5k/makefile-executor.el][makefile-executor.el]] :: works only with Makefiles
- [[https://github.com/tarsius/imake][imake]] :: works only with Makefiles that are formatted with a =help:= target
- [[https://github.com/emacs-taskrunner/emacs-taskrunner ][Taskrunner project ]] :: requires ivy or helm, but perhaps I could use the underlying infrastructure to good ol’ [[help:completing-read ][completing-read ]]
Note: Someday I may want to convert my =Makefile= projects to [[https://taskfile.dev/ ][Taskfile ]].
*** Makefile Completion
This magic script is what Bash uses for completion when you type =make= and hit the TAB:
#+name : make-targets
#+begin_src shell :tangle no
make -qRrp : 2> /dev/null | awk -F':' '/ ^[a-zA-Z0-9][^$#\\/\\t=]*:([^=]|$)/ {split($1,A,/ / );for(i in A)print A[i]}'
#+end_src
Which makes it easy to get a list of completions for my compile function:
#+begin_src emacs-lisp :noweb yes
(defun ha--makefile-completions ()
"Returns a list of targets from the Makefile in the current directory."
(when (file-exists-p "Makefile")
(--map (format "make -k %s" it)
(shell-command-to-list "<<make-targets >>"))))
#+end_src
*** Python Tox Completion
Let’ s just grab the environments to run:
#+begin_src emacs-lisp
(defun ha--toxfile-completions ()
"Returns a list of targets from the tox.ini in the current directory."
(when (file-exists-p "tox.ini")
(--map (format "tox -e %s" it)
(shell-command-to-list "tox -a"))))
#+end_src
2021-11-09 00:02:13 +00:00
* Languages
2022-10-26 04:38:48 +00:00
Simple to configure languages go here. More advanced languages go into their own files… eventually.
2022-12-03 01:10:11 +00:00
** Configuration Files
So many configuration files to track:
#+begin_src emacs-lisp
(use-package conf-mode
:mode (("\\.conf\\'" . conf-space-mode)
("\\.repo\\'" . conf-unix-mode)
("\\.setup.*\\'" . conf-space-mode)))
#+end_src
** JSON
While interested in the [[https://github.com/emacs-tree-sitter/tree-sitter-langs ][tree-sitter ]] extensions for JSON, e.g. =json-ts-mode= , that comes with Emacs 29, I’ ll deal with what is bundled now.
2023-08-11 23:29:45 +00:00
However, what about taking a buffer of JSON data, and whittling it down with [[https://jqlang.github.io/jq/ ][jq ]]?
#+begin_src emacs-lisp
(defun ha-json-buffer-to-jq (query)
"Runs JSON buffer with QUERY through an external `jq' program.
Attempts to find the first JSON object in the buffer, and limits
the data to that region. The `jq' program is the first found in
the standard path."
(interactive "sjq Query: ")
(let (s e)
(save-excursion
(if (region-active-p)
(setq s (region-beginning)
e (region-end))
(goto-char (point-min))
(unless (looking-at "{")
(re-search-forward "{")
(goto-char (match-beginning 0)))
(setq s (point))
2024-02-09 20:05:13 +00:00
;; Jump forward using the evil-jump-item ... change this to one
;; of the functions in thing-at-point?
(when (fboundp 'evil-jump-item)
(evil-jump-item))
2023-08-11 23:29:45 +00:00
(setq e (1+ (point))))
;; (narrow-to-region s e)
(shell-command-on-region s e (concat "jq " query) nil t "*jq errors* "))))
(ha-local-leader :keymaps '(js-json-mode-map json-ts-mode-map)
"j" 'ha-json-buffer-to-jq)
#+end_src
This means, that some data like:
#+begin_src json :tangle no
{
"common_id": "GMC|F2BADC23|64D52BF7|awardlateengine",
"data": {
"name": "Create And Wait for Service Image",
"description": "Creates a new Service Image using IMaaS",
"long_description": "This job creates a new yawxway service image with name yawxway-howard.abrams-test and docker-dev-artifactory.workday.com/dev/yawxway-service:latest docker url in development folder",
"job_id": "5e077245-0f4a-4dc9-b473-ce3ec0b811ba",
"state": "success",
"progress": "100",
"timeout": {
"seconds": 300,
"strategy": "real_time",
"elapsed": 1291.8504
},
"started_at": "2023-08-10T16:20:49Z",
"finished_at": "2023-08-10T16:42:20Z",
"links": [
{
"rel": "child-4aa5978c-4537-4aa9-9568-041ad97c2374",
"href": "https://eng501.garmet.howardism.org/api/jobs/4aa5978c-4537-4aa9-9568-041ad97c2374"
},
{
"rel": "project",
"href": "https://eng501.garmet.howardism.org/api/projects/8abe0f6e-161e-4423-ab27-d4fb0d5cfd0c"
},
{
"rel": "details",
"href": "https://eng501.garmet.howardism.org/api/jobs/5e077245-0f4a-4dc9-b473-ce3ec0b811ba/details"
}
],
"tags": [
"foobar", "birdie"
],
"progress_comment": null,
"children": [
{
"id": "4aa5978c-4537-4aa9-9568-041ad97c2374"
}
]
},
"status": "SUCCESS"
}
#+end_src
I can type, ~, j~ and then type =.data.timeout.seconds= and end up with:
#+begin_src json
300
#+end_src
2022-09-01 04:47:46 +00:00
** Markdown
2023-08-17 00:39:36 +00:00
Most project =README= files and other documentation use [[https://jblevins.org/projects/markdown-mode/ ][markdown-mode ]]. Note that the /preview/ is based on =multimarkdown= , when needs to be /pre-installed/ , for instance:
#+begin_src sh
brew install multimarkdown
#+end_src
Also, I like Markdown is look like a word processor, similarly to my org files:
2022-09-01 04:47:46 +00:00
#+begin_src emacs-lisp
(use-package markdown-mode
2022-12-03 19:14:42 +00:00
:straight (:host github :repo "jrblevin/markdown-mode")
:mode ((rx ".md" string-end) . gfm-mode)
2023-08-17 00:39:36 +00:00
:init (setq markdown-command "multimarkdown"
markdown-header-scaling t)
2022-09-01 04:47:46 +00:00
:general
(:states 'normal :no-autoload t :keymaps 'markdown-mode-map
2023-08-17 00:39:36 +00:00
", l" '("insert link" . markdown-insert-link) ; Also C-c C-l
", i" '("insert image" . markdown-insert-image) ; Also C-c C-i
2023-03-20 21:01:40 +00:00
;; SPC u 3 , h for a third-level header:
", h" '("insert header" . markdown-insert-header-dwim)
2023-08-17 00:39:36 +00:00
", t" '(:ignore t :which-key "toggles")
", t t" '("toggle markup" . markdown-toggle-markup-hiding)
", t u" '("toggle urls" . markdown-toggle-markup-url-hiding)
", t i" '("toggle images" . markdown-toggle-markup-inline-images)
", t m" '("toggle math" . markdown-toggle-markup-math-hiding)
", d" '("do" . markdown-do)
2023-03-20 21:01:40 +00:00
", e" '("export" . markdown-export)
2023-08-17 00:39:36 +00:00
", p" '("preview" . markdown-preview)))
2022-09-01 04:47:46 +00:00
#+end_src
Note that the markdown-specific commands use the ~C-c C-c~ and ~C-c C-s~ prefixes.
2023-05-03 04:55:22 +00:00
2023-08-17 00:39:36 +00:00
With the =markdown-header-scaling= set, we no longer need to color the headers in Markdown.
#+begin_src emacs-lisp
(use-package markdown-mode
:config
(when window-system
(let ((default-color (face-attribute 'default :foreground)))
(set-face-attribute 'markdown-header-face nil
:font ha-variable-header-font
:foreground default-color))))
#+end_src
2023-05-03 04:55:22 +00:00
Using [[https://polymode.github.io/ ][polymode ]], let’ s add syntax coloring to Markdown code blocks similar to what we do with Org:
#+begin_src emacs-lisp
(use-package polymode
:config
(define-hostmode poly-markdown-hostmode :mode 'markdown-mode)
(define-auto-innermode poly-markdown-fenced-code-innermode
:head-matcher (cons "^[ \t]*\\(```{?[[:alpha:]].* \n\\)" 1)
:tail-matcher (cons "^[ \t]*\\(```\\)[ \t]* $" 1)
:mode-matcher (cons "```[ \t]*{?\\(?:lang *= *\\)?\\([^ \t\n;= ,}]+\\)" 1)
:head-mode 'host
:tail-mode 'host)
(define-polymode poly-markdown-mode
:hostmode 'poly-markdown-hostmode
:innermodes '(poly-markdown-fenced-code-innermode))
:mode ((rx ".md" string-end) . poly-markdown-mode))
#+end_src
2023-08-17 00:39:36 +00:00
** ReStructured Text
Support for [[https://docutils.sourceforge.io/rst.html ][reStructuredText ]] is [[https://www.emacswiki.org/emacs/reStructuredText ][well supported ]] in Emacs.
#+begin_src emacs-lisp
(use-package rst
:config
(set-face-attribute 'rst-literal nil :font ha-fixed-font))
#+end_src
2023-05-03 04:55:22 +00:00
** YAML
2023-08-11 23:39:15 +00:00
Doing a lot of [[https://github.com/yoshiki/yaml-mode ][YAML work ]], but this =yaml-mode= project needs a new maintainer, 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 :tangle no
2023-09-08 17:58:17 +00:00
(when (treesit-available-p)
2023-08-11 23:39:15 +00:00
(use-package yaml-ts-mode
:mode ((rx ".y" (optional "a") "ml" string-end)
(rx (optional ".") "yamllint"))
:hook (yaml-ts-mode . display-line-numbers-mode)))
#+end_src
Get the latest version of =yaml-mode= :
2023-05-03 04:55:22 +00:00
#+begin_src emacs-lisp
2023-08-11 23:39:15 +00:00
(use-package yaml-mode
:mode (rx ".y" (optional "a") "ml" string-end)
(rx (optional ".") "yamllint")
:hook (yaml-mode . display-line-numbers-mode))
#+end_src
2023-05-03 04:55:22 +00:00
2023-08-11 23:39:15 +00:00
And we hook
#+begin_src emacs-lisp
(use-package yaml-pro
:straight (:host github :repo "zkry/yaml-pro")
:after yaml-mode
:hook (yaml-mode . yaml-pro-mode))
2023-05-03 04:55:22 +00:00
#+end_src
This comes with a list of nice refactoring features that we can attach to the local leader:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2023-09-08 17:58:17 +00:00
(when (treesit-available-p)
2023-05-03 04:55:22 +00:00
(use-package yaml-pro
:config
(ha-local-leader :keymaps 'yaml-pro-ts-mode-map
"u" '("up" . yaml-pro-ts-up-level) ; C-c C-u
"j" '("next" . yaml-pro-ts-next-subtree) ; C-c C-n
"k" '("previous" . yaml-pro-ts-prev-subtree) ; C-c C-p
"m" '("mark tree" . yaml-pro-ts-mark-subtree) ; C-c C-@
2023-05-25 17:38:46 +00:00
"d" '("kill subtree" . yaml-pro-kill-subtree) ; C-c C-x C-w
2023-05-03 04:55:22 +00:00
"y" '("paste tree" . yaml-pro-ts-paste-subtree) ; C-c C-x C-y
"'" '("edit" . yaml-pro-edit-ts-scalar) ; C-c '
"r" '(:ignore t :which-key "refactor")
"r k" '("move up" . yaml-pro-ts-move-subtree-up) ; s-↑
"r j" '("move down" . yaml-pro-ts-move-subtree-down) ; s-↓
;; "r " '("" . yaml-pro-ts-meta-return) ; M-<return >
"r c" '("convolute" . yaml-pro-ts-convolute-tree) ; M-?
"r i" '("indent" . yaml-pro-ts-indent-subtree) ; C-c >
"r o" '("outdent" . yaml-pro-ts-unindent-subtree)))) ; C-c <
2022-06-18 00:25:47 +00:00
#+end_src
2023-05-03 04:55:22 +00:00
Seems like I need a predicate to check for the existence of Tree Sitter support?
Note that these packages need the following to run properly:
2022-12-03 19:14:42 +00:00
#+begin_src sh
pip install yamllint
#+end_src
2023-05-03 04:55:22 +00:00
** Jinja2
A lot of projects (like Ansible and Zuul) uses [[https://jinja.palletsprojects.com ][Jinja2 ]] with YAML, so we install the [[https://github.com/paradoxxxzero/jinja2-mode ][jinja2-mode ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-10-26 04:38:48 +00:00
(use-package jinja2-mode
:mode (rx ".j2" string-end))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 04:04:41 +00:00
2023-05-03 04:55:22 +00:00
Jinja is a /templating/ system that integrates /inside/ formats like JSON, HTML or YAML.
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:
#+begin_src emacs-lisp
(use-package polymode
:config
2023-08-11 23:39:15 +00:00
(define-hostmode poly-yaml-hostmode :mode 'yaml-mode)
2023-05-03 04:55:22 +00:00
(defcustom pm-inner/jinja2
(pm-inner-chunkmode :mode #'jinja2-mode
:head-matcher "{[%{#][+-]?"
:tail-matcher "[+-]?[%}#]}"
:head-mode 'body
:tail-mode 'body
:head-adjust-face t)
"Jinja2 chunk."
:group 'innermodes
:type 'object)
2023-08-11 23:39:15 +00:00
2023-05-03 04:55:22 +00:00
(define-polymode poly-yaml-jinja2-mode
:hostmode 'poly-yaml-hostmode
:innermodes '(pm-inner/jinja2))
:mode ((rx ".y" (optional "a") "ml" string-end) . poly-yaml-jinja2-mode))
#+end_src
** 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.
2023-08-11 23:39:15 +00:00
#+begin_src emacs-lisp :tangle no
2022-04-12 04:04:41 +00:00
(use-package ansible
2023-08-11 23:39:15 +00:00
:straight (:host github :repo "k1LoW/emacs-ansible")
:defer t
:mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
2022-04-12 04:04:41 +00:00
:config
2023-05-03 04:55:22 +00:00
(setq ansible-vault-password-file "~/.ansible-vault-passfile")
2022-04-12 04:04:41 +00:00
(ha-leader "t y" 'ansible))
2022-06-18 00:25:47 +00:00
#+end_src
2023-05-03 04:55:22 +00:00
2022-04-12 17:42:01 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no
2022-04-12 17:42:01 +00:00
((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 04:04:41 +00:00
2022-10-26 04:38:48 +00:00
The YAML files get access Ansible’ s documentation using the [[https://github.com/emacsorphanage/ansible-doc ][ansible-doc ]] project:
2023-08-11 23:39:15 +00:00
#+begin_src emacs-lisp :tangle no
2022-04-12 04:04:41 +00:00
(use-package ansible-doc
2023-05-03 04:55:22 +00:00
:hook (ansible-mode . ansible-doc-mode)
2023-08-11 23:39:15 +00:00
:after ansible
2022-04-12 04:04:41 +00:00
:config
2023-05-03 04:55:22 +00:00
(ha-local-leader :keymaps 'ansible-key-map
2022-04-12 04:04:41 +00:00
"d" '(:ignore t :which-key "docs")
"d d" 'ansible-doc))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 04:04:41 +00:00
2022-10-26 04:38:48 +00:00
Can we integrate Ansible with LSP using [[https://github.com/ansible/ansible-language-server ][ansible-language-server ]] project (see [[https://emacs-lsp.github.io/lsp-mode/page/lsp-ansible/ ][this documentation ]])?
2023-05-03 04:55:22 +00:00
Using =npm= to install the program:
2022-10-26 04:38:48 +00:00
#+begin_src sh
2023-08-11 23:39:15 +00:00
npm install -g @ansible/ansible-language-server
2022-10-26 04:38:48 +00:00
#+end_src
2023-05-03 04:55:22 +00:00
But … will I get some use out of this? I’ ll come back to it later.
2023-03-24 18:01:01 +00:00
** Docker
Edit =Dockerfiles= with the [[https://github.com/spotify/dockerfile-mode ][dockerfile-mode ]] project:
#+BEGIN_SRC emacs-lisp
2023-05-01 22:42:57 +00:00
(use-package dockerfile-mode
2023-03-24 18:01:01 +00:00
:mode (rx string-start "Dockerfile")
:config
2023-05-01 22:42:57 +00:00
(make-local-variable 'docker-image-name)
(defvaralias 'docker-image-name 'dockerfile-image-name nil)
2023-05-03 04:56:43 +00:00
(ha-local-leader :keymaps 'dockerfile-mode-map
2023-05-01 22:42:57 +00:00
"b" '("build" . dockerfile-build-buffer)
2023-05-03 04:56:43 +00:00
"B" '("build no cache" . dockerfile-build-no-cache-buffer)
2023-05-01 22:42:57 +00:00
"t" '("insert build tag" . ha-dockerfile-build-insert-header))
2023-03-24 18:01:01 +00:00
(defun ha-dockerfile-build-insert-header (image-name)
"Prepends the default Dockerfile image name at the top of a file."
(interactive "sDefault image name: ")
(save-excursion
(goto-char (point-min))
(insert (format "## -*- dockerfile-image-name: \"%s\" -* -" image-name))
2023-05-03 04:56:43 +00:00
(newline))))
2023-03-24 18:01:01 +00:00
#+END_SRC
/Control/ Docker from Emacs using the [[https://github.com/Silex/docker.el ][docker.el ]] project:
#+BEGIN_SRC emacs-lisp
(use-package docker
:commands docker
:config
2023-05-01 22:42:57 +00:00
(ha-leader "a d" 'docker))
2023-03-24 18:01:01 +00:00
#+END_SRC
Unclear whether I want to Tramp into a running container:
#+BEGIN_SRC emacs-lisp :tangle no
(use-package docker-tramp
:defer t
:after docker)
#+END_SRC
2021-11-09 00:02:13 +00:00
** Shell Scripts
2022-09-28 20:19:09 +00:00
While I don't like writing them, I can't get away from them. Check out the goodies in [[https://www.youtube.com/watch?v=LTC6SP7R1hA&t=5s ][this video ]].
2021-11-09 00:02:13 +00:00
2022-09-28 20:19:09 +00:00
While filename extensions work fine most of the time, I don't like to pre-pend =.sh= to the shell scripts I write, and instead, would like to associate =shell-mode= with all files in a =bin= directory:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-09 00:02:13 +00:00
(use-package sh-mode
2021-11-14 06:18:19 +00:00
:straight (:type built-in)
2021-11-09 00:02:13 +00:00
:mode (rx (or (seq ".sh" eol)
"/bin/ "))
2022-09-28 20:19:09 +00:00
:init
(setq sh-basic-offset 2
sh-indentation 2)
2021-11-12 21:05:31 +00:00
:config
(ha-auto-insert-file (rx (or (seq ".sh" eol)
2022-09-28 20:19:09 +00:00
"/bin/ "))
"sh-mode.sh")
2021-11-09 00:02:13 +00:00
:hook
(after-save . executable-make-buffer-file-executable-if-script-p))
2022-06-18 00:25:47 +00:00
#+end_src
2022-09-28 20:19:09 +00:00
*Note:* we make the script /executable/ by default. See [[https://emacsredux.com/blog/2021/09/29/make-script-files-executable-automatically/ ][this essay ]] for details, but it turns on the executable bit if the script has a shebang at the top of the file.
The [[https://www.shellcheck.net/ ][shellcheck ]] project integrates with [[Flycheck ]]. First, install the executable into the system, for instance, on a Mac:
#+begin_src sh
brew install shellcheck
#+end_src
And we can enable it:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-09-28 20:19:09 +00:00
(flycheck-may-enable-checker 'sh-shellcheck)
#+end_src
Place the following /on a line/ before a shell script warning to ignore it:
#+begin_src sh
# shellcheck disable=SC2116,SC2086
#+end_src
See [[https://github.com/koalaman/shellcheck/wiki/Ignore ][this page ]] for details.
Integration with the [[https://github.com/bash-lsp/bash-language-server ][Bash LSP implementation ]]. First, install that too:
#+begin_src sh
brew install bash-language-server
#+end_src
** Fish Shell
2022-10-10 21:41:52 +00:00
I think the [[https://fishshell.com/ ][fish shell ]] is an interesting experiment (and I appreciate the basics that come with [[https://github.com/emacsmirror/fish-mode ][fish-mode ]]).
#+begin_src emacs-lisp
2021-11-12 21:05:31 +00:00
(use-package fish-mode
:mode (rx ".fish" eol)
:config
2021-11-14 06:14:55 +00:00
(ha-auto-insert-file (rx ".fish") "fish-mode.sh")
2021-11-12 21:05:31 +00:00
:hook
(fish-mode . (lambda () (add-hook 'before-save-hook 'fish_indent-before-save))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-09 00:02:13 +00:00
* Technical Artifacts :noexport:
2022-06-18 00:25:47 +00:00
Provide a name to =require= this code.
#+begin_src emacs-lisp :exports none
(provide 'ha-programming)
;;; ha-programming.el ends here
#+end_src
2021-11-09 00:02:13 +00:00
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
2024-03-07 04:02:25 +00:00
#+description : A literate programming file for helping me program.
2021-11-09 00:02:13 +00:00
2024-03-07 04:02:25 +00:00
#+property : header-args:sh :tangle no
#+property : header-args:emacs-lisp :tangle yes
#+property : header-args :results none :eval no-export :comments no mkdirp yes
2021-11-09 00:02:13 +00:00
2024-03-07 04:02:25 +00:00
#+options : num:nil toc:t todo:nil tasks:nil tags:nil date:nil
#+options : skip:nil author:nil email:nil creator:nil timestamp:nil
#+infojs_opt : view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js