Configuring Ansible and YAML
My day job now involves a lot of Ansible, and I’ve been struggling to get the right balance here.
Much of the conflict stems from whether to use Tree Sitter for the YAML mode or not.
Moving by lines is our default navigation mode, but for Yaml and Python, that doesn’t have s-expressions, we need something to move around by blocks. The spatial navigation project attempts to address this. The problem is how to bind the functions.
The obvious keybindings are M-h/j/k/l
… but that is used … well, somewhat. In org files, this is a way to move the outline of subtrees around. Useful, and spatial-navigate
wouldn’t be helpful in org files anyway.
(use-package spatial-navigate
:straight (:repo "https://codeberg.org/ideasman42/emacs-spatial-navigate")
:config
(pretty-hydra-define spatial-navigate (:color amaranth :quit-key "q")
("Box"
(("k" spatial-navigate-backward-vertical-box "up")
("j" spatial-navigate-forward-vertical-box "Down")
("h" spatial-navigate-backward-horizontal-box "Down")
("l" spatial-navigate-forward-horizontal-box "Down"))
"Bar"
(("K" spatial-navigate-backward-vertical-bar "up")
("J" spatial-navigate-forward-vertical-bar "Down")
("H" spatial-navigate-backward-horizontal-bar "Down")
("L" spatial-navigate-forward-horizontal-bar "Down")))))
And we can attach this menu to the “g” section:
(use-package spatial-navigate
:general
(:states '(normal visual motion operator)
"g t" '("spatial nav" . spatial-navigate/body)))
The Highlight-Indentation project highlights each of the indent levels, which could be helpful for these modes:
(use-package highlight-indentation
:straight (:host github :repo "antonj/Highlight-Indentation-for-Emacs")
:hook ((yaml-mode . highlight-indentation-mode)
(yaml-ts-mode . highlight-indentation-mode)
(python-mode . highlight-indentation-mode)))
This project has another display feature, which just lines up the current level. So I’ve created a toggle for this:
(use-package highlight-indentation
:config
(setq highlight-indentation-blank-lines t)
(set-face-background 'highlight-indentation-face "#332c26")
(set-face-background 'highlight-indentation-current-column-face "#66615c")
(defun ha-toggle-highlight-indentation ()
"Toggles through the indentation modes."
(interactive)
(cond
(highlight-indentation-mode
(progn
(highlight-indentation-mode -1)
(highlight-indentation-current-column-mode 1)))
(highlight-indentation-current-column-mode
(progn
(highlight-indentation-mode -1)
(highlight-indentation-current-column-mode -1)))
(t
(progn
(highlight-indentation-mode 1)
(highlight-indentation-current-column-mode -1)))))
(ha-leader "t i" '("show indents" . ha-toggle-highlight-indentation)))
Doing a lot of YAML work, but the yaml-mode
project needs a new maintainer, so I might as well switch over to the T version.
, so I’ve switch to yaml-pro that is now based on Tree Sitter. Let’s make sure the Tree-Sitter version works:
(when (treesit-available-p)
(use-package yaml-ts-mode
:straight (:type built-in)
:mode ((rx ".yamllint")
(rx ".y" (optional "a") "ml" string-end))
:hook (yaml-ts-mode . (lambda () (mixed-pitch-mode -1)))
:mode-hydra
((:foreign-keys run)
("Simple"
(("l" ha-yaml-next-section "Next section")
("h" ha-yaml-prev-section "Previous"))))))
Allow this mode in Org blocks:
(add-to-list 'org-babel-load-languages '(yaml-ts . t))
And we hook
(use-package yaml-pro
:straight (:host github :repo "zkry/yaml-pro")
:after yaml-ts-mode
:hook ((yaml-ts-mode . yaml-pro-ts-mode)
(yaml-mode . yaml-pro-mode)))
Since I can never remember too many keybindings for particular nodes, we create a Hydra just for it.
(use-package major-mode-hydra
:after yaml-pro
:config
(major-mode-hydra-define yaml-ts-mode (:foreign-keys run)
("Navigation"
(("u" yaml-pro-ts-up-level "Up level" :color pink) ; C-c C-u
("J" yaml-pro-ts-next-subtree "Next subtree" :color pink) ; C-c C-n
("K" yaml-pro-ts-prev-subtree "Previous" :color pink)) ; C-c C-p
"Editing"
(("m" yaml-pro-ts-mark-subtree "Mark subtree") ; C-c C-@
("x" yaml-pro-ts-kill-subtree "Kill subtree") ; C-c C-x C-w
("p" yaml-pro-ts-paste-subtree "Paste subtree")) ; C-c C-x C-y
"Insert"
(("e" yaml-pro-edit-ts-scalar "Edit item") ; C-c '
("o" yaml-pro-ts-meta-return "New list item"))
"Refactor"
(("r" yaml-pro-ts-move-subtree-up "Raise subtree")
("t" yaml-pro-ts-move-subtree-down "Lower subtree")
("," combobulate-hydra/body ">>>"))
"Documentation"
(("d" hydra-devdocs/body "Devdocs")))))
Note that these packages need the following to run properly:
pip install yamllint
A lot of projects (like Ansible and Zuul) uses Jinja2 with YAML, so we first install the jinja2-mode:
(use-package jinja2-mode
:mode (rx ".j2" string-end))
Jinja is a template system that integrates inside formats like JSON, HTML or YAML. The polymode project glues modes like jinja2-mode to yaml-mode.
I adapted this code from the poly-ansible project:
(use-package polymode
:config
(define-hostmode poly-yaml-hostmode :mode 'yaml-ts-mode)
(defcustom pm-inner/jinja2
(pm-inner-chunkmode :mode #'jinja2-mode
:head-matcher (rx "{"
(or "%" "{" "#")
(optional (or "+" "-")))
:tail-matcher (rx (optional (or "+" "-"))
(or "%" "}" "#")
"}")
: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))
(major-mode-hydra-define+ yaml-ts-mode nil
("Extensions" (("j" poly-yaml-jinja2-mode "Jinja2")))))
We need to make sure the mixed-pitch-mode
doesn’t screw things up.
(add-hook 'poly-yaml-jinja2-mode-hook (lambda () (mixed-pitch-mode -1)))
We can hook this up to Org, via:
(add-to-list 'org-babel-load-languages '(poly-yaml-jinja2 . t))
Now we can use either yaml-ts
or poly-yaml-jinja2
(which perhaps we should make an alias?):
---
# Let's see how this works
- name: Busta move
debug:
msg: >-
This {{ adjective }} {{ noun }} {{ verb }} the ball."
{% for x in does %}
What is this about?
{% endfor %}
vars:
adjective: small
noun: squirrel
verb: ate
Do I consider all YAML files an Ansible file needing ansible-mode? Maybe we just have a toggle for when we want the Ansible feature.
(use-package ansible
:straight (:host github :repo "k1LoW/emacs-ansible")
;; :mode ((rx (or "playbooks" "roles") (one-or-more any) ".y" (optional "a") "ml") . ansible-mode)
:config
(setq ansible-vault-password-file "~/.ansible-vault-passfile")
(major-mode-hydra-define+ yaml-ts-mode nil
("Extensions" (("a" ansible "Ansible"))))
(ha-leader "t y" 'ansible))
The ansible-vault-password-file variable needs to change per project, so let’s use the .dir-locals.el
file, for instance:
((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
The YAML files get access Ansible’s documentation using the ansible-doc project (that accesses the ansible-doc interface):
(use-package ansible-doc
:after yaml-ts-mode
:hook (yaml-ts-mode . ansible-doc-mode)
:config
;; (add-to-list 'exec-path (expand-file-name "~/.local/share/mise/installs/python/3.10/bin/ansible-doc"))
(major-mode-hydra-define+ yaml-ts-mode nil
("Documentation"
(("D" ansible-doc "Ansible")))))
Can we integrate Ansible with LSP using ansible-language-server project (see this documentation)?
Using npm
to install the program:
npm install -g @ansible/ansible-language-server
But … will I get some use out of this? I’ll come back to it later.
Let’s provide a name so that the file can be required: