hamacs/ha-programming.org

386 lines
15 KiB
Org Mode
Raw Normal View History

#+TITLE: General Programming Configuration
#+AUTHOR: Howard X. Abrams
#+EMAIL: howard.abrams@gmail.com
#+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 <howard.abrams@gmail.com>
;; 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:
;; /Users/howard.abrams/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...
* 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"))
#+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 :tangle no
(use-package alert)
(use-package beep
:straight nil)
#+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, =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
(general-evil-define-key 'normal prog-mode-map
:prefix "SPC m"
"c" '("comment line" . ha-comment-line))
#+END_SRC
** Evaluation
Typical keybindings for all programming modes:
#+BEGIN_SRC emacs-lisp
(general-evil-define-key 'normal prog-mode-map
:prefix "SPC m"
"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/Ansible/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
And, finally, we install [[https://github.com/k1LoW/emacs-ansible][Ansible]] as well:
#+BEGIN_SRC emacs-lisp
(use-package ansible
:init
(setq ansible-vault-password-file "~/work/5/wpc5/deploy/playbooks/.vault-password")
:config
(add-hook 'yaml-mode-hook '(lambda () (ansible 1))))
#+END_SRC
And some special keybindings to encrypt/decrypt files:
#+BEGIN_SRC emacs-lisp
(general-evil-define-key 'normal ansible-mode-map
:prefix "SPC m"
"x" '(:ignore t :which-key "decryption")
"x d" '("decrypt buffer" . ansible-decrypt-buffer)
"x e" '("encrypt buffer" . ansible-encrypt-buffer))
#+END_SRC
** Emacs Lisp
Why yes, I do find I code a lot in Emacs...
*** 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 (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
(general-evil-define-key 'normal prog-mode-map
:prefix "SPC m"
"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 nil
:mode (rx (or (seq ".sh" eol)
"/bin/"))
: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.
* 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