#+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 ;; 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) :keymaps 'prog-mode-map :prefix "SPC m") #+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") :config (direnv-mode)) #+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 :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 :hook ((lsp-mode . lsp-enable-which-key-integration)) :commands lsp) #+END_SRC I will want to start adding commands under my =SPC m= mode-specific key sequence leader, but ... later. 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) (add-hook 'lsp-mode-hook 'lsp-ui-mode)) #+END_SRC 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. The [[https://github.com/emacs-lsp/lsp-ui/blob/master/lsp-ui-imenu.el][lsp-imenu]] 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 (add-hook 'lsp-after-open-hook 'lsp-enable-imenu)) #+END_SRC Do we want to use a debugger? #+BEGIN_SRC emacs-lisp :tangle no (use-package dap-mode) #+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 (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/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 (ha-prog-leader "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... #+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 (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 '(("" 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 "") '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