diff --git a/bootstrap.org b/bootstrap.org index 6469f40..388e9d2 100644 --- a/bootstrap.org +++ b/bootstrap.org @@ -116,7 +116,7 @@ The following loads the rest of my org-mode literate files. I add them as they a ;; "org-publishing.org" "ha-org-sprint.org" ;; "capturing-notes.org" - ;; "general-programming.org" + "ha-programming.org" ;; "ha-agendas.org" ;; "ha-email.org" ;; "ha-irc.org" diff --git a/ha-config.org b/ha-config.org index 9addb5a..e804de2 100644 --- a/ha-config.org +++ b/ha-config.org @@ -80,12 +80,13 @@ While I would rather program my configurations, sometimes the Emacs menu system Rewriting my shell scripts in Emacs Lisp uses my [[https://gitlab.com/howardabrams/emacs-piper][emacs-piper project]], and this code spills into my configuration code, so let's load it now: -#+BEGIN_SRC emacs-lisp :tangle no -(use-package piper - :load-path "~/other/emacs-piper/" - :commands shell-command-to-list ; I use this function quite a bit - :bind (:map evil-normal-state-map - ("|" . piper-user-interface))) +#+BEGIN_SRC emacs-lisp + (use-package piper + :straight nil + :load-path "~/other/emacs-piper/" + :commands shell-command-to-list ; I use this function quite a bit + :bind (:map evil-normal-state-map + ("|" . piper-user-interface))) #+END_SRC ** Yet Another Snippet System (YASnippets) Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to convert templates into text: diff --git a/ha-org-sprint.org b/ha-org-sprint.org index 41d5df1..86fa50a 100644 --- a/ha-org-sprint.org +++ b/ha-org-sprint.org @@ -185,7 +185,7 @@ Emacs have an internal rep of a time. (defun get-date-time (date) "Many functions can't deal with dates as string, so this will parse DATE if it is a string, or return the value given otherwise." - (if (stringp date) + (if (and data (stringp date)) (->> date ; Shame that encode-time parse-time-string ; can't take a string, as (-take 6) ; this seems excessive... diff --git a/ha-programming.org b/ha-programming.org new file mode 100644 index 0000000..a389536 --- /dev/null +++ b/ha-programming.org @@ -0,0 +1,383 @@ +#+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 +;; 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: +;; /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 +(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 '(("" 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 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