hamacs/ha-programming.org
Howard Abrams 59e72f33e4 Eschewing flycheck-yamllint as it can be part of flycheck
Also, the polymode causing me more grief now, so I'm removing it
temporarily.
2023-01-09 20:03:42 -08:00

686 lines
31 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: General Programming Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-10-26
A literate programming file for helping me program.
#+begin_src emacs-lisp :exports none
;;; general-programming --- Configuration for general languages. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 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/general-programming.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
Seems that all programming interfaces and workflows behave similarly. One other helper routine is a =general= macro for org-mode files:
#+begin_src emacs-lisp
(general-create-definer ha-prog-leader
:states '(normal visual motion)
:keymaps 'prog-mode-map
:prefix "SPC m"
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+end_src
* General
The following work for all programming languages.
** direnv
Farm off commands into /virtual environments/:
#+begin_src emacs-lisp
(use-package direnv
:init
(setq direnv--executable "/usr/local/bin/direnv"
direnv-always-show-summary t
direnv-show-paths-in-summary t)
: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 Im now pushing much of this to LSP, so speed is less of an issue. What about when I am not using LSP? Also, since Ive 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
Im 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-lookup)
:config
(ha-prog-leader
"d" '(:ignore t :which-key "docs")
"d e" '("eldoc" . eldoc)
"d d" '("open" . devdocs-lookup)
"d p" '("peruse" . devdocs-peruse)
"d i" '("install" . devdocs-install)
"d u" '("update" . devdocs-update-all)
"d x" '("uninstall" . devdocs-delete)
"d s" '("search" . devdocs-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 cant 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-prog-leader
"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= doesnt 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 well see if I need those.
** Navigation with 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
:init
(setq dumb-jump-prefer-searcher 'rg)
:config
(setq xref-show-definitions-function #'xref-show-definitions-completing-read)
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
;; (add-to-list 'evil-goto-definition-functions #'dumb-jump)
;; 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-prog-leader
"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." 'xref-find-definitions
"g>" 'xref-find-definitions-other-window
"g," 'xref-go-back
"g<" 'xref-go-forward
"g/" 'xref-find-references
"g?" 'xref-find-references-and-replace
"gh" 'xref-find-apropos
"gb" 'xref-go-back))
#+end_src
I have two different /jumping/ systems, the [[info:emacs#Xref][Xref interface]] and Evils. While comparable goals, they are behave different. Lets 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]] … doesnt 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 dont 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-prog-leader
"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
"SPC m w r" '("restart" . lsp-reconnect)
"SPC m w b" '("events" . lsp-events-buffer)
"SPC m w e" '("errors" . lsp-stderr-buffer)
"SPC m w q" '("quit" . lsp-shutdown)
"SPC m w Q" '("quit all" . lsp-shutdown-all)
"SPC m l r" '("rename" . lsp-rename)
"SPC m l f" '("format" . lsp-format)
"SPC m l a" '("actions" . lsp-code-actions)
"SPC m l i" '("imports" . lsp-code-action-organize-imports)
"SPC m 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 =SPC m= 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-prog-leader
"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")))
: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
(use-package iedit
:config
(ha-leader "s e" '("iedit" . iedit-mode)))
#+end_src
** 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
(ha-prog-leader "c" '("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-prog-leader
"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" '("last s-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 dont work on the Mac, and that is my current workhorse.
** 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 doesnt 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 projects root and get targets from a =Makefile=, etc. Well 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 (projectile-project-root)))
;; 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 (projectile-project-root)))
(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 (projectile-project-name)))
;; (add-to-list 'compile-history full-command)
(let ((command (replace-regexp-in-string rx-compile-to-vterm "" full-command)))
(ha-shell-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 (projectile-project-name)))
(let ((command (replace-regexp-in-string rx-compile-to-eshell "" full-command)))
(ha-eshell-send command project-dir)))
#+end_src
And lets 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 peoples 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
Lets 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, Ill deal with what is bundled now.
** Markdown
All the READMEs and other documentation use [[https://jblevins.org/projects/markdown-mode/][markdown-mode]].
#+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")
:general
(:states 'normal :no-autoload t :keymaps 'markdown-mode-map
"SPC m l" '("insert link" . markdown-insert-link)
;; SPC u 3 SPC m h for a third-level header:
"SPC m h" '("insert header" . markdown-insert-header-dwim)
"SPC m e" '("export" . markdown-export)
"SPC m p" '("preview" . markdown-export-and-preview)))
#+end_src
Note that the markdown-specific commands use the ~C-c C-c~ and ~C-c C-s~ prefixes.
** Ansible
Doing a lot of [[https://github.com/yoshiki/yaml-mode][YAML work]], but this project needs a new maintainer.
#+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
Note this needs the following to run properly:
#+begin_src sh
pip install yamllint
#+end_src
Ansible uses Jinja, 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
Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/emacs-ansible][ansible-mode]]?
#+begin_src emacs-lisp
(use-package ansible
:init
(setq ansible-vault-password-file "~/.ansible-vault-passfile")
;; :hook (yaml-mode . ansible-mode)
:config
(ha-leader "t y" 'ansible))
#+end_src
The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable needs to change /per project/, so lets use the =.dir-locals.el= file, for instance:
#+begin_src emacs-lisp :tangle no
((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
#+end_src
The YAML files get access Ansibles documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project:
#+begin_src emacs-lisp
(use-package ansible-doc
:hook (yaml-mode . ansible-doc-mode)
:config
(ha-local-leader :keymaps 'yaml-mode-map
"d" '(:ignore t :which-key "docs")
"d d" 'ansible-doc))
#+end_src
The [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project uses [[https://polymode.github.io/][polymode]], gluing [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]] into [[https://github.com/yoshiki/yaml-mode][yaml-mode]].
#+begin_src emacs-lisp :tangle no
(use-package polymode)
(use-package poly-ansible
:after polymode
:straight (:host github :repo "emacsmirror/poly-ansible")
:hook ((yaml-mode . poly-ansible-mode)
(poly-ansible-mode . font-lock-update)))
#+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]])?
First, use =npm= to install the program:
#+begin_src sh
npm installl -g @ansible/ansible-language-server
#+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 yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js