hamacs/ha-programming.org
2022-02-25 17:12:18 -08:00

586 lines
24 KiB
Org Mode
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
#+FILETAGS: :emacs:
A literate programming file for helping me program.
#+BEGIN_SRC emacs-lisp :exports none
;;; general-programming.el --- A literate programming file for helping me program. -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2020 Howard X. Abrams
;;
;; 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. However...
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]] only checks for misspellings in comments. Useful!
#+BEGIN_SRC emacs-lisp
(use-package flyspell
:hook (prog-mode . flyspell-prog-mode))
#+END_SRC
** Flymake
Grab the latest version of [[https://www.emacswiki.org/emacs/FlyMake][Flymake]] in order to integrate with LSP. While we are at it, lets add some keybindings.
#+BEGIN_SRC emacs-lisp :tangle no
(use-package flymake
:config
(ha-prog-leader
">" '("next problem" . flymake-goto-next-error)
"<" '("previous problem" . flymake-goto-prev-error)
"p" '(:ignore t :which-key "problems")
"p s" '("start checking" . flymake-start)
"p t" '("toggle flymake" . flymake-mode)
"p p" '("show problem" . flymake-show-diagnostic)
"p P" '("list all problems" . flymake-show-buffer-diagnostics)
"p l" '("show log buffer" . flymake-switch-to-log-buffer)
"p b" '("show log buffer" . flymake-running-backends)))
#+END_SRC
The question is why not use the newer [[https://www.flycheck.org/][flycheck]]? Speed used to be the advantage, however, Im now pushing this stuff to LSP, so speed is less of an issue. However, what about when I am not using LSP?
#+BEGIN_SRC emacs-lisp
(use-package flycheck
: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-prog-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 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
Ive used the [[http://kapeli.com/][Dash]] API Documentation browser (an external application) with Emacs, however, this is only available for Mac.
#+BEGIN_SRC emacs-lisp :tangle no
(use-package dash-at-point
:commands (dash-at-point)
:config
(define-key evil-normal-state-map (kbd "g D") 'dash-at-point))
#+END_SRC
However, Im interested in using [[https://devdocs.io/][devdocs]] instead, which is similar, but keeps it all /inside/ Emacs (and works on my Linux system). There are seems to be two competing Emacs projects for this.
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
:after evil
:config
(define-key evil-normal-state-map (kbd "g D") 'devdocs-lookup)
(ha-prog-leader
"d" '(:ignore t :which-key "docs")
"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
:config
(define-key evil-normal-state-map (kbd "g D") 'devdocs-browser-open)
(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
** 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...).
Instead, we install [[https://emacs-lsp.github.io/lsp-mode/][LSP Mode]] (and friends), which simplifies my configuration:
#+BEGIN_SRC emacs-lisp
(use-package lsp-mode
:commands lsp
: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")
: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:
#+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
*** 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
(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
(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
*** Treemacs
The [[https://github.com/emacs-lsp/lsp-treemacs][lsp-treemacs]] offers a project-specific structure oriented to the code:
#+BEGIN_SRC emacs-lisp
(use-package lsp-treemacs
:config
(ha-prog-leader
"0" '("treemacs" . lsp-treemacs-symbols)))
#+END_SRC
*** Debugging
Do we want to use a debugger?
#+BEGIN_SRC emacs-lisp :tangle no
(use-package dap-mode
:init
(require 'dap-python))
#+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
(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
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" . eval-print-last-sexp))
#+END_SRC
** Ligatures
The idea of using math symbols for a programming languages keywords is /cute/, but confusing when working with other people, and they are looking at my screen:
#+BEGIN_SRC emacs-lisp :tangle no
(set-ligatures! 'python-mode nil)
#+END_SRC
The rest of the ligature system in Doom is nice.
** 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
(setq doom-modeline-lsp t)
(setq doom-modeline-env-version t)
#+END_SRC
* Languages
Simple to configure languages go here. More advanced stuff will go in their own files...eventually.
** YAML and Jinja
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 "\\.ya?ml\\'")
#+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" eol))
#+END_SRC
** Emacs Lisp
Why yes, I do find I code a lot in Emacs...
#+BEGIN_SRC emacs-lisp
(ha-auto-insert-file (rx ".el" eol) "emacs-lisp-mode.el")
#+END_SRC
However, most of my Emacs Lisp code is in literate org files.
*** Clever Parenthesis
The [[https://github.com/luxbock/evil-cleverparens][evil-cleverparens]] solves having me create keybindings to the [[https://github.com/Fuco1/smartparens][smartparens]] project by updating the evil states with Lisp-specific bindings.
#+BEGIN_SRC emacs-lisp
(use-package evil-cleverparens
:after smartparens-mode
:custom
evil-cleverparens-use-additional-bindings t
evil-cleverparens-use-additional-movement-keys t
evil-cleverparens-use-s-and-S nil ; using evil-sniper
:init
(require 'evil-cleverparens-text-objects)
:hook (prog-mode . evil-cleverparens-mode)) ;; All the languages!
;; Otherwise: (emacs-lisp-mode . evil-cleverparens-mode)
#+END_SRC
I would like to have a list of what keybindings do what:
- ~M-h~ / ~M-l~ move back/forward by functions
- ~H~ / ~L~ move back/forward by s-expression
- ~M-i~ insert at the beginning of the form
- ~M-a~ appends at the end of the form
- ~M-o~ new form after the current sexp
- ~M-O~ new form /before/ the current sexp
- ~M-j~ / ~M-k~ drags /thing at point/ and back and forth in the form
- ~>~ slurp forward if at the end of form, at beginning, it barfs backwards
- ~<~ slurp backward if at start of form, at the end, it barfs forwards
- ~M-(~ / ~M-)~ wraps next/previous form in parens (braces and brackets work too)
- ~x~ unwraps if the point is on the =(= of an expression.
- ~D~ deletes an entire s-expression, but this can depend on the position of the point.
The other advantage is moving around by s-expressions. This takes a little getting used to, for instance:
- ~[~ and ~]~ move from paren to paren, essentially, from s-expression.
- ~H~ and ~L~ act similarly to the above.
- ~(~ and ~)~ move up to the parent s-expression
Other nifty keybindings that I need to commit to muscle memory include:
| ~M-q~ | =sp-indent-defun= |
| ~M-J~ | =sp-join-sexp= |
| ~M-s~ | =sp-splice-sexp= |
| ~M-S~ | =sp-split-sexp= |
| ~M-t~ | =sp-transpose-sexp= |
| ~M-v~ | =sp-convolute-sexp= |
| ~M-r~ | =sp-raise-sexp= |
***** Eval Current Expression
A feature I enjoyed from Spacemacs is the ability to evaluate the s-expression currently containing the point. Not sure how how they made it, but cleverparens can help:
#+BEGIN_SRC emacs-lisp
(defun ha-eval-current-expression ()
"Evaluates the expression the point is currently 'in'.
It does this, by jumping to the end of the current
expression (using evil-cleverparens), and evaluating what it
finds at that point."
(interactive)
(save-excursion
(evil-cp-next-closing)
(evil-cp-forward-sexp)
(call-interactively 'eval-last-sexp)))
#+END_SRC
And we just need to bind it. The following is Doom-specific:
#+BEGIN_SRC emacs-lisp
(ha-prog-leader
"e c" '("current" . ha-eval-current-expression))
#+END_SRC
*** Dim those Parenthesis
The [[https://github.com/tarsius/paren-face][paren-face]] project lowers the color level of parenthesis which I personally find better.
#+BEGIN_SRC emacs-lisp
(use-package paren-face
:hook (emacs-lisp-mode . paren-face-mode))
#+END_SRC
I'm going to play with the [[https://github.com/DogLooksGood/parinfer-mode][parinfer]] package.
*** Lispy
Sacha had an [[https://sachachua.com/blog/2021/04/emacs-making-a-hydra-cheatsheet-for-lispy/][interesting idea]] to /generate/ a Hydra from a mode map:
#+NAME: bindings
| key | function | column |
|-----+-------------------------------+----------|
| < | lispy-barf |   |
| A | lispy-beginning-of-defun |   |
| j | lispy-down |   |
| Z | lispy-edebug-stop |   |
| B | lispy-ediff-regions |   |
| G | lispy-goto-local |   |
| h | lispy-left |   |
| N | lispy-narrow |   |
| y | lispy-occur |   |
| o | lispy-other-mode |   |
| J | lispy-outline-next |   |
| K | lispy-outline-prev |   |
| P | lispy-paste |   |
| l | lispy-right |   |
| I | lispy-shifttab |   |
| > | lispy-slurp |   |
| SPC | lispy-space |   |
| xB | lispy-store-region-and-buffer |   |
| u | lispy-undo |   |
| k | lispy-up |   |
| v | lispy-view |   |
| V | lispy-visit |   |
| W | lispy-widen |   |
| D | pop-tag-mark |   |
| x | see |   |
| L | unbound |   |
| U | unbound |   |
| X | unbound |   |
| Y | unbound |   |
| H | lispy-ace-symbol-replace | Edit |
| c | lispy-clone | Edit |
| C | lispy-convolute | Edit |
| n | lispy-new-copy | Edit |
| O | lispy-oneline | Edit |
| r | lispy-raise | Edit |
| R | lispy-raise-some | Edit |
| \ | lispy-splice | Edit |
| S | lispy-stringify | Edit |
| i | lispy-tab | Edit |
| xj | lispy-debug-step-in | Eval |
| xe | lispy-edebug | Eval |
| xT | lispy-ert | Eval |
| e | lispy-eval | Eval |
| E | lispy-eval-and-insert | Eval |
| xr | lispy-eval-and-replace | Eval |
| p | lispy-eval-other-window | Eval |
| q | lispy-ace-paren | Move |
| z | lispy-knight | Move |
| s | lispy-move-down | Move |
| w | lispy-move-up | Move |
| t | lispy-teleport | Move |
| Q | lispy-ace-char | Nav |
| - | lispy-ace-subword | Nav |
| a | lispy-ace-symbol | Nav |
| b | lispy-back | Nav |
| d | lispy-different | Nav |
| f | lispy-flow | Nav |
| F | lispy-follow | Nav |
| g | lispy-goto | Nav |
| xb | lispy-bind-variable | Refactor |
| xf | lispy-flatten | Refactor |
| xc | lispy-to-cond | Refactor |
| xd | lispy-to-defun | Refactor |
| xi | lispy-to-ifs | Refactor |
| xl | lispy-to-lambda | Refactor |
| xu | lispy-unbind-variable | Refactor |
| M | lispy-multiline | Other |
| xh | lispy-describe | Other |
| m | lispy-mark-list | Other |
#+BEGIN_SRC emacs-lisp :var bindings=bindings :colnames yes :tangle no
(defvar my-lispy-bindings bindings)
(defvar ha-hydra-lispy-bindings
(cl-loop for x in my-lispy-bindings
unless (string= "" (elt x 2))
collect
(list (car x)
(intern (elt x 1))
(when (string-match "lispy-\\(?:eval-\\)?\\(.+\\)"
(elt x 1))
(match-string 1 (elt x 1)))
:column
(elt x 2)))
"Collection of memorable Lispy functions")
(eval
`(defhydra
,(append '(("<f14>" nil :exit t)) ha-hydra-lispy-bindings )
))
(funcall defhydra
`(my/lispy-cheat-sheet (:hint nil :foreign-keys run)
))
(with-eval-after-load "lispy"
(define-key lispy-mode-map (kbd "<f14>") 'my/lispy-cheat-sheet/body))
#+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 in order 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