432 lines
18 KiB
Org Mode
432 lines
18 KiB
Org Mode
#+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.
|
||
** 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.
|
||
** 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.
|
||
** 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
|
||
** 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
|
||
|
||
** 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
|
||
* Languages
|
||
Simple to configure languages go here. More advanced stuff will go in their own files… eventually.
|
||
** 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.
|
||
|
||
While filename extensions work fine most of the time, I don't like to pre-pend =.sh= to the few 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/"))
|
||
: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 appears that the executable bit is only turned on if the script has a shebang at the top of the file.
|
||
** Fish Shell
|
||
#+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
|