#+title:  General Programming Configuration
#+author: Howard X. Abrams
#+date:   2020-10-26
#+tags: emacs programming yaml ansible docker json

A literate programming file for helping me program.

#+begin_src emacs-lisp :exports none
  ;;; general-programming --- Configuration for general languages. -*- lexical-binding: t; -*-
  ;;
  ;; © 2020-2023 Howard X. Abrams
  ;;   Licensed under a Creative Commons Attribution 4.0 International License.
  ;;   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:
  ;;            ~/other/hamacs/ha-programming.org
  ;;       And tangle the file to recreate this one.
  ;;
  ;;; Code:
#+end_src
* Introduction
Configuration for programming interfaces and workflows that behave similarly.
* General
The following work for all programming languages.
** 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
** direnv
Farm off commands into /virtual environments/:
#+begin_src emacs-lisp :tangle no
  (use-package direnv
    :init
    (setq direnv-always-show-summary t
          direnv-show-paths-in-summary t)
    (if (file-exists-p "/opt/homebrew")
        (setq direnv--executable "/opt/homebrew/bin/direnv")
      (setq direnv--executable "/usr/local/bin/direnv"))
    :config
    (direnv-mode))
#+end_src
** Spell Checking Comments
The [[https://www.emacswiki.org/emacs/FlySpell#h5o-2][flyspell-prog-mode]] checks for misspellings in comments.

#+begin_src emacs-lisp
  (use-package flyspell
    :hook (prog-mode . flyspell-prog-mode))
#+end_src
** Flycheck
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.

#+begin_src emacs-lisp
  (use-package flycheck
    :init
    (setq next-error-message-highlight t)
    :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
    (defun flycheck-enable-checker ()
      "Not sure why flycheck disables working checkers."
      (interactive)
      (let (( current-prefix-arg '(4))) ; C-u
        (call-interactively 'flycheck-disable-checker)))

    (flymake-mode -1)
    (global-flycheck-mode)
    (ha-leader "t c" 'flycheck-mode)

    (ha-leader
      ">" '("next problem" . flycheck-next-error)
      "<" '("previous problem" . flycheck-previous-error)

      "e" '(:ignore t :which-key "errors")
      "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")

      "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)))
#+end_src
** Documentation
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.
#+begin_src emacs-lisp
  (use-package devdocs
    :general (:states 'normal
                      "gD" '("devdocs" . devdocs-lookup))

    :config
    (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")))))
#+end_src

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.
#+begin_src emacs-lisp :tangle no
  (use-package devdocs-browser
    :general (:states 'normal "gD" 'devdocs-browser-open)

    :config
    (ha-local-leader :keymaps 'prog-mode-map
      "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)))
#+end_src
** Code Folding
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
  (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))
#+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
  (use-package origami
    :init
    (setq origami-fold-replacement "⤵")
    :hook (prog-mode . origami-mode))
#+end_src
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.
** 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
  (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))
#+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))
#+end_src
*** Tree Sitter
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.
**** Operating System Part
Install the binary for the [[https://tree-sitter.github.io/][tree-sitter project]]. For instance:
#+begin_src sh
  brew install tree-sitter npm # Since most support packages need that too.
#+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!

Next, using the =tree-sitter= command line tool, create the [[/Users/howard.abrams/Library/Application Support/tree-sitter/config.json][config.json]] file:
#+begin_src sh
  tree-sitter init-config
#+end_src

Normally, you would need to  add all the projects to directory clones in =~/src=, e.g.
#+begin_src sh :dir ~/src
  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

Seems that Docker is a bit of an odd-ball:
#+begin_src sh
  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
  for TSS in ~/src/tree-sitter-*
  do
    cd $TSS
    NAME=$(pwd | sed 's/.*-//')

    git pull origin
    npm install || cargo build || make install   # Various build processes!?

    echo "Do we need to copy the library into ~/.emacs.d/tree-sitter/$NAME ?"
    # 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
  done
#+end_src
At this point, we can now parse stuff using: =tree-sitter parse <source-code-file>=
**** Emacs Part
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]]:
#+begin_src emacs-lisp :tangle no
  (when (string-search "TREE_SITTER" system-configuration-features)
    (use-package treesit
      :straight (:type built-in)
      :preface
      (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")))

      (defun mp-setup-install-grammars ()
        "Install Tree-sitter grammars if they are absent."
        (interactive)
        (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))

      :config
      (mp-setup-install-grammars)))
#+end_src

And enable the languages:
#+begin_src emacs-lisp :tangle no
  (when (treesit-available-p)
    (use-package tree-sitter-langs
      :after treesit
      :config
      (global-tree-sitter-mode)))
#+end_src
*** 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]] →
#+begin_src emacs-lisp :tangle no
  (when (treesit-available-p)
    (use-package combobulate
      :straight (:host github :repo "mickeynp/combobulate")
      :after treesit
      ;; :hook ((css-ts-mode    . combobulate-mode)
      ;;        (json-ts-mode   . combobulate-mode)
      ;;        (python-ts-mode . combobulate-mode)
      ;;        (yaml-ts-mode   . combobulate-mode))
     ))
#+end_src

I can create a /helper function/ to allow me to jump to various types of—well, /types/:
#+begin_src emacs-lisp :tangle no
  (when (treesit-available-p)
    (use-package combobulate
      :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)))))))
#+end_src

Now, I can create an /interface/ of keystrokes to jump around like a boss:
#+begin_src emacs-lisp :tangle no
  (when (treesit-available-p)
    (use-package combobulate
      :general
      (:states 'visual :keymaps 'combobulate-key-map
               "o" '("mark node" . combobulate-mark-node-dwim))              ; Mark symbol since "o" doesn't do anything
      (: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]]:
#+begin_src emacs-lisp :tangle no
  (when (and (treesit-available-p) (fboundp 'evil-define-text-object))
    (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"))
      (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"))
      (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:
#+begin_src emacs-lisp :tangle no :tangle no
  (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))))
#+end_src
*** dumb-jump
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:
  - 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
    (setq dumb-jump-prefer-searcher 'rg
          xref-history-storage #'xref-window-local-history
          xref-show-definitions-function #'xref-show-definitions-completing-read)

    (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)

    ;; 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)

    (ha-local-leader :keymaps 'prog-mode-map
      "s"  '(:ignore t :which-key "search")
      "s s" '("search"       . xref-find-apropos)
      "s d" '("definitions"  . xref-find-definitions)
      "s o" '("other window" . xref-find-definitions-other-window)
      "s r" '("references"   . xref-find-references)
      "s b" '("back"         . xref-go-back)
      "s f" '("forward"      . xref-go-forward))

    :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)))
#+end_src

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.
** Language Server Protocol (LSP) Integration
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…).

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.
*** LSP
#+begin_src emacs-lisp
  (use-package lsp-mode
    :commands (lsp lsp-deferred)
    :init
    ;; 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")

    :config
    (global-set-key (kbd "s-m") 'lsp)
    (ha-local-leader :keymaps 'prog-mode-map
      "w"  '(:ignore t :which-key "lsp")
      "l"  '(:ignore t :which-key "lsp")
      "ws" '("start" . lsp))

    ;; The following leader-like keys, are only available when I have
    ;; started LSP, and is an alternate to Command-m:
    :general
    (:states 'normal :keymaps 'lsp-mode-map
             ", 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))

   :hook ((lsp-mode . lsp-enable-which-key-integration)))
#+end_src
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.
*** 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]]:

#+begin_src emacs-lisp :tangle no
  (use-package company-lsp
    :config
    (push 'company-lsp company-backends))
#+end_src
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.

*** 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:

#+begin_src emacs-lisp :tangle no
  (use-package lsp-ui-imenu
      :straight nil
      :after lsp-ui
      :config
      (ha-local-leader :keymaps 'prog-mode-map
        "g"  '(:ignore t :which-key "goto")
        "g m" '("imenu" . lsp-ui-imenu))
      (add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
#+end_src
*** 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
** 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.

#+begin_src emacs-lisp
  (use-package alert
    :init
    (setq alert-default-style
          (if (ha-running-on-macos?)
              'osx-notifier
            'libnotify)))

  (use-package beep
    :straight nil   ; Already in the load-path
    :hook (after-init . (lambda () (beep-when-finished "Emacs has started." "Eemacs has started")))
    :config
    (dolist (func '(org-publish
                    org-publish-all
                    org-publish-project
                    compile
                    shell-command))
      (advice-add func :around #'beep-when-runs-too-long)))
#+end_src
While that code /advices/ the publishing and compile commands, I may want to add more.
** iEdit
While there are language-specific ways to rename variables and functions, [[https://github.com/victorhge/iedit][iedit]] is often sufficient.
#+begin_src emacs-lisp :tangle no
  (use-package iedit
    :config
    (ha-leader "s e" '("iedit" . iedit-mode)))
#+end_src

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
  (when (fboundp 'evil-mode)
    (use-package evil-iedit-state
      :after iedit
      :general
      (:states 'visual "E" '("iedit" . evil-iedit-state/iedit-mode))))
#+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
** Case Conversion
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.
#+begin_src emacs-lisp
  (use-package string-inflection
    :general
    (:states '(normal visual motion operator)
             "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)))
#+end_src
I would like to have this bound on the ~g~ sequence, but that is crowded.

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).
** Commenting
I like =comment-dwim= (~M-;~), and I like =comment-box=, but I have an odd personal style that I like to codify:
#+begin_src emacs-lisp
  (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)))
#+end_src
And a keybinding:
#+begin_src emacs-lisp :tangle no
  (ha-local-leader :keymaps 'prog-mode-map
    "c"  '(:ignore t :which-key "comment")
    "c l" '("comment line" . ha-comment-line))
#+end_src
** Evaluation
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.
#+begin_src emacs-lisp
  (defun ha-eval-print-last-sexp (&optional internal-arg)
    "Evaluate the expression located before the point.
  Insert results back into the buffer at the end of the line after
  a comment."
    (interactive)
    (save-excursion
      (eval-print-last-sexp internal-arg))
    (end-of-line)
    (insert "  ")
    (insert comment-start)
    (insert "⟹ ")
    (dotimes (i 2)
      (next-line)
      (join-line)))
#+end_src

Typical keybindings for all programming modes:
#+begin_src emacs-lisp
  (ha-local-leader :keymaps 'prog-mode-map
     "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)
     "e e" '("eval exp" . eval-last-sexp)
     "e p" '("print s-exp" . ha-eval-print-last-sexp))
#+end_src
** Ligatures
The idea of using math symbols for a programming languages keywords is /cute/, but can be confusing, so I use it sparingly:
#+begin_src emacs-lisp
  (defun ha-prettify-prog ()
    "Extends the `prettify-symbols-alist' for programming."
    (mapc (lambda (pair) (push pair prettify-symbols-alist))
          '(("lambda" . "𝝀")
            (">=" . "≥")
            ("<=" . "≤")
            ("!=" . "≠")))
    (prettify-symbols-mode))

  (add-hook 'prog-mode-hook 'ha-prettify-prog)
#+end_src

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.
#+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
    (ligature-set-ligatures
     'prog-mode '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                  ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                  "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                  "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                  "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                  "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                  "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                  "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                  ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                  "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                  "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                  "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                  "\\\\" "://"))
    ;; 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)
    (add-hook 'prog-mode-hook #'ha-mac-litagure-workaround))
#+end_src

The unicode-fonts package rejigs the internal tables Emacs uses to pick better fonts for unicode codepoint ranges.
#+begin_src emacs-lisp :tangle no
  (use-package unicode-fonts
    :config
    (ignore-errors
      (unicode-fonts-setup)))
#+end_src
** 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:
#+begin_src emacs-lisp :tangle no
  ((nil . ((compile-command . "make -k ")
           (compile-command-list . ("ansible-playbook playbooks/confluence_test.yml"
                                "ansible-playbook playbooks/refresh_inventory.yml")))))
#+end_src

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'.")

  (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)))
#+end_src

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."
    (let ((default-directory (project-root (project-current))))
      ;; 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

My replacement to [[help:compile][compile]] uses my new =completing-read= function:
#+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)))
    (let ((default-directory (project-root (project-current))))
      (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)))))
#+end_src

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
      (setq project-dir (project-name (project-current))))

    ;; (add-to-list 'compile-history full-command)
    (let ((command (replace-regexp-in-string rx-compile-to-vterm "" full-command)))
      (ha-ssh-send command project-dir)))
#+end_src

And what about sending the command to Eshell as well?
#+begin_src emacs-lisp
  (defvar rx-compile-to-eshell (rx "|" (0+ space) "s" (0+ space) line-end))

  (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
      (setq project-dir (project-name (project-current))))

    (let ((command (replace-regexp-in-string rx-compile-to-eshell "" full-command)))
      (ha-eshell-send command project-dir)))
#+end_src
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
* Languages
Simple to configure languages go here. More advanced languages go into their own files… eventually.
** 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.

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))
          ;; 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))
          (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
** Markdown
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:
#+begin_src emacs-lisp
  (use-package markdown-mode
    :straight (:host github :repo "jrblevin/markdown-mode")
    :mode ((rx ".md" string-end) . gfm-mode)
    :init (setq markdown-command "multimarkdown"
                markdown-header-scaling t)
    :general
    (:states 'normal :no-autoload t :keymaps 'markdown-mode-map
             ", l" '("insert link" . markdown-insert-link) ; Also C-c C-l
             ", i" '("insert image" . markdown-insert-image) ; Also C-c C-i
             ;; SPC u 3 , h for a third-level header:
             ", h" '("insert header" . markdown-insert-header-dwim)
             ", 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)
             ", e" '("export" . markdown-export)
             ", p" '("preview" . markdown-preview)))
#+end_src
Note that the markdown-specific commands use the ~C-c C-c~ and  ~C-c C-s~ prefixes.

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

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
** 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
** YAML
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
  (when (treesit-available-p)
    (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=:
#+begin_src emacs-lisp
  (use-package yaml-mode
        :mode (rx ".y" (optional "a") "ml" string-end)
              (rx (optional ".") "yamllint")
        :hook (yaml-mode . display-line-numbers-mode))
#+end_src

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))
#+end_src

This comes with a list of nice refactoring features that we can attach to the local leader:
#+begin_src emacs-lisp
  (when (treesit-available-p)
    (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-@
        "d" '("kill subtree" . yaml-pro-kill-subtree)     ; C-c C-x C-w
        "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 <
#+end_src
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:
#+begin_src sh
  pip install yamllint
#+end_src
** 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]]:
#+begin_src emacs-lisp
  (use-package jinja2-mode
    :mode (rx ".j2" string-end))
#+end_src

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
    (define-hostmode poly-yaml-hostmode :mode 'yaml-mode)
    (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)

    (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.
#+begin_src emacs-lisp :tangle no
  (use-package ansible
    :straight (:host github :repo "k1LoW/emacs-ansible")
    :defer t
    :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
    :config
    (setq ansible-vault-password-file "~/.ansible-vault-passfile")
    (ha-leader "t y" 'ansible))
#+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:
#+begin_src emacs-lisp :tangle no
  ((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
#+end_src

The YAML files get access Ansible’s documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project:
#+begin_src emacs-lisp :tangle no
  (use-package ansible-doc
    :hook (ansible-mode . ansible-doc-mode)
    :after ansible
    :config
    (ha-local-leader :keymaps 'ansible-key-map
      "d"  '(:ignore t :which-key "docs")
      "d d" 'ansible-doc))
#+end_src

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]])?

Using =npm= to install the program:
#+begin_src sh
  npm install -g @ansible/ansible-language-server
#+end_src
But … will I get some use out of this? I’ll come back to it later.
** Docker
Edit =Dockerfiles= with the [[https://github.com/spotify/dockerfile-mode][dockerfile-mode]] project:
#+BEGIN_SRC emacs-lisp
  (use-package dockerfile-mode
    :mode (rx string-start "Dockerfile")
    :config
    (make-local-variable 'docker-image-name)
    (defvaralias 'docker-image-name 'dockerfile-image-name nil)

    (ha-local-leader :keymaps 'dockerfile-mode-map
      "b" '("build" . dockerfile-build-buffer)
      "B" '("build no cache" . dockerfile-build-no-cache-buffer)
      "t" '("insert build tag" . ha-dockerfile-build-insert-header))

    (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))
        (newline))))
#+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
    (ha-leader "a d" 'docker))
#+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

** Shell Scripts
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]].

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:
#+begin_src emacs-lisp
  (use-package sh-mode
    :straight (:type built-in)
    :mode (rx (or (seq ".sh" eol)
                  "/bin/"))
    :init
    (setq sh-basic-offset 2
          sh-indentation 2)
    :config
    (ha-auto-insert-file (rx (or (seq ".sh" eol)
                                 "/bin/"))
                         "sh-mode.sh")
    :hook
    (after-save . executable-make-buffer-file-executable-if-script-p))
#+end_src
*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:
#+begin_src emacs-lisp
  (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
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
  (use-package fish-mode
    :mode (rx ".fish" eol)
    :config
    (ha-auto-insert-file (rx ".fish") "fish-mode.sh")
    :hook
    (fish-mode . (lambda () (add-hook 'before-save-hook 'fish_indent-before-save))))
#+end_src
* Technical Artifacts                                :noexport:
Provide a name to =require= this code.
#+begin_src emacs-lisp :exports none
  (provide 'ha-programming)
  ;;; ha-programming.el ends here
#+end_src

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~

#+description: A literate programming file for helping me program.

#+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

#+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
