#+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 ;; 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 "" :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 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 (flymake-mode -1) (global-flycheck-mode) (ha-leader "t c" 'flycheck-mode) (ha-leader ">" '("next problem" . flycheck-next-error) "<" '("previous problem" . flycheck-previous-error) "P" '(:ignore t :which-key "problems") "P b" '("error buffer" . flycheck-buffer) "P c" '("clear" . flycheck-clear) "P n" '("next" . flycheck-next-error) "P N" '("next" . flycheck-next-error) "P p" '("previous" . flycheck-previous-error) "P P" '("previous" . flycheck-previous-error) "P l" '("list all" . flycheck-list-errors) "P y" '("copy errors" . flycheck-copy-errors-as-kill) "P s" '("select checker" . flycheck-select-checker) "P ?" '("describe checker" . flycheck-describe-checker) "P h" '("display error" . flycheck-display-error-at-point) "P e" '("explain error" . flycheck-explain-error-at-point) "P H" '("help" . display-local-help) "P i" '("manual" . flycheck-manual) "P V" '("version" . flycheck-version) "P v" '("verify-setup" . flycheck-verify-setup) "P x" '("disable-checker" . flycheck-disable-checker) "P 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-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 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-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= 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. ** 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 just 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) (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-pop-marker-stack)) :general (:states 'normal "gh" 'xref-find-apropos "gb" 'xref-pop-marker-stack)) #+end_src ** 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, it was originally designed for VS Code and probably Python, but we now 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. *** eglot The [[https://github.com/joaotavora/eglot][eglot]] package usually connects to Emacs’ standard command interface, so the eglot-specific code is mostly in controlling the backend servers. That said, it has a couple of =eglot-= commands that I want easy access to: #+begin_src emacs-lisp (use-package eglot :config (ha-prog-leader "w" '(:ignore t :which-key "eglot") "ws" '("start" . eglot) "wr" '("restart" . eglot-reconnect) "wb" '("events" . eglot-events-buffer) "we" '("errors" . eglot-stderr-buffer) "wq" '("quit" . eglot-shutdown) "wQ" '("quit all" . eglot-shutdown-all) "r" '("rename" . eglot-rename) "=" '("format" . eglot-format) "a" '("code actions" . eglot-code-actions) "i" '("imports" . eglot-code-action-organize-imports))) #+end_src This requires the [[http://company-mode.github.io/][company]] completion backend: #+begin_src emacs-lisp (use-package company :after eglot :hook (after-init . global-company-mode) :bind ("s-." . company-complete)) #+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) (interactive "r") (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. The results are inserted 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 Eventually, I want to 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. ** Task Runner I've replaced my home-grown compilation list code with a more versatile [[https://github.com/emacs-taskrunner/emacs-taskrunner][Taskrunner project]]. #+begin_src emacs-lisp :tangle no (setq ivy-taskrunner-notifications-on t ivy-taskrunner-doit-bin-path "/usr/local/bin/doit") #+end_src Doom provides basic support, but we need more keybindings: #+begin_src emacs-lisp :tangle no (map! :leader :prefix "p" :desc "Project tasks" "Z" 'ivy-taskrunner :desc "Reun last task" "z" 'ivy-taskrunner-rerun-last-command) #+end_src While my company is typically using =Rakefile= and =Makefile= in the top-level project, I want to have my personal tasks set per-project as well. For that, I thought about using [[https://pydoit.org/][doit]], where I would just create a =dodo.py= file that contains: #+begin_src python :tangle no def hello(): """This command greets you.""" return { 'actions': [ 'echo hello' ], } #+end_src * Languages Simple to configure languages go here. More advanced stuff will go in their own files… eventually. ** Markdown All the READMEs and other documentation use [[https://jblevins.org/projects/markdown-mode/][markdown-mode]]. #+begin_src emacs-lisp (use-package markdown-mode :mode ("README\\.md\\'" . 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)) #+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 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 However, let’s have all YAML files able to access Ansible’s 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 (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 ** 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 While I thought the [[https://fishshell.com/][fish shell]] was an interesting experiment (and I appreciated the basics that come with [[https://github.com/emacsmirror/fish-mode][fish-mode]]), I always find myself writing the =sh= way of doing things. When fish completely /broke/ on my laptop, I figured it was just not worth the effort to maintain. I’ll be removing this code soon enough. #+begin_src emacs-lisp :tangle no (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