Convert to lower-case #+BEGIN_SRC blocks

While I was at it, I address some prose-specific comments like passive
sentences and weasel words.
This commit is contained in:
Howard Abrams 2022-06-17 17:25:47 -07:00
parent 0e130dd024
commit ffbd253e65
35 changed files with 1567 additions and 1616 deletions

View file

@ -1,13 +1,12 @@
#+TITLE: My Emacs Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-11-01 November
#+TAGS: emacs
My Emacs configuration, that I'm cheekily calling /hamacs/ is a literate programming model heavily inspired by my recent journey into [[https://www.youtube.com/watch?v=LKegZI9vWUU][Henrik Lissner's]] [[https://github.com/hlissner/doom-emacs][Doom Emacs]] and [[https://www.spacemacs.org/][Spacemacs]]. I used both extensively, but decided that I would /roll my own/ as Emacs people tend to be /control freaks/ (at least a little bit).
The other advantage to rolling yer own is that you are more likely to /use/ what you add, leading to less bloat, and a more fun experience.
The other advantage to rolling yer own is that you may /use/ what you add, leading to less bloat, and a more fun experience.
Why yes, feel free to steal whatever you find interesting, as sharing is what makes our community great. Notice that functions and features that I have written begin with ~ha-~, however, everything else is either /stock Emacs/ or a /package/ that I download using [[https://github.com/raxod502/straight.el][straight]] (see [[file:bootstrap.org][bootstrap]] for how) and configured with [[https://github.com/jwiegley/use-package][use-package]] (see either [[https://ianyepan.github.io/posts/setting-up-use-package/][this introduction]] or [[https://www.emacswiki.org/emacs/UsePackage][this wiki page]] for details)... meaning that most blocks of code should /just work/ on its own.
Why yes, feel free to steal whatever you find interesting, as sharing is what makes our community great. Notice that functions and features that I have written begin with ~ha-~, but everything else is either /stock Emacs/ or a /package/ that I download using [[https://github.com/raxod502/straight.el][straight]] (see [[file:bootstrap.org][bootstrap]] for how) and configured with [[https://github.com/jwiegley/use-package][use-package]] (see either [[https://ianyepan.github.io/posts/setting-up-use-package/][this introduction]] or [[https://www.emacswiki.org/emacs/UsePackage][this wiki page]] for details)… meaning that most blocks of code should work on its own.
Hit me up with questions, =@howardabrams=. If you want to try this out, after installing Emacs, and cloning this repo, run:
#+BEGIN_SRC sh
./initialize
@ -17,7 +16,7 @@ This creates [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] that starts the pro
- [[file:bootstrap.org][bootstrap]] :: configures =straight= and loads basic libraries the rest of the code depends on. It then loads the following files in order:
- [[file:ha-config.org][config]] :: contains /most/ of my configuration, setting up my sequence key menus, evil, etc.
- [[file:ha-display.org][display]] :: sets up the visual aspects of an Emacs GUI, including themes, fonts and the dashboard.
- [[file:ha-org.org][org]] :: configures the basics for org-mode formatted files. Specific features, however, come from their own files, however.
- [[file:ha-org.org][org]] :: configures the basics for org-mode formatted files. Specific features come from their own files.
- [[file:ha-org-word-processor.org][org-word-processor]] :: attempts to make Org files /visually/ look like a word processor, including turning off the colors for headers, and instead increasing their size.
- [[file:ha-org-clipboard.org][org-clipboard]] :: automatically converting HTML from a clipboard into Org-formatted content.
- [[file:ha-org-journaling.org][org-journaling]] :: for writing journal entries and tasks.
@ -35,6 +34,5 @@ This creates [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] that starts the pro
- [[file:ha-programming-elisp.org][programming-elisp]] :: additions to Emacs Lisp programming.
- [[file:ha-programming-python.org][programming-python]] :: configuration for working with Python and LSP.
- [[file:ha-programming-scheme.org][programming-scheme]] :: configuration for Racket.
- [[file:ha-aux-apps.org][aux-apps]] :: additional application configuration.
*Note:* Other functions and files come from essays written on [[http://www.howardism.org][my blog]]. To help with this, see [[file:support/final-initialize.el][support/final-initialize.el]] file.

View file

@ -1,15 +1,14 @@
#+TITLE: My Emacs Bootstrap
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-10-08
#+FILETAGS: :emacs:
A literate programming file for bootstraping my Emacs Configuration.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; bootstrap.el --- file for bootstraping my Emacs Configuration
;;
;; © 2021-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,13 +22,13 @@ A literate programming file for bootstraping my Emacs Configuration.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
This file contains all the variable definitions and library loading for the other files in my project.
** Straight Package Installer
I'm going to be installing everything using the [[https://github.com/raxod502/straight.el#getting-started][straight.el]] for package installation and management. However, before I could tangle these org files, I needed to have =straight= grab the latest =org=, so the following initialization code is actually in [[file:initialize][initialize]], but the good stuff is:
I'm going to be installing everything using the [[https://github.com/raxod502/straight.el#getting-started][straight.el]] for package installation and management. Before I can /tangle/ these files, I need =straight= to grab the latest =org=, so the following initialization code is actually in [[file:initialize][initialize]], but if you are reading this online, configuring =straight= amounts to the following:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(defvar bootstrap-version)
(let ((bootstrap-file
@ -43,24 +42,24 @@ I'm going to be installing everything using the [[https://github.com/raxod502/st
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
#+END_SRC
Let's get the Straight project working with =use-package=:
#+end_src
#+BEGIN_SRC emacs-lisp :tangle no
To get the Straight project working with =use-package=:
#+begin_src emacs-lisp :tangle no
(straight-use-package 'use-package)
#+END_SRC
#+end_src
While that enables the =:straight t= extension to =use-package=, let's just have that be the default:
#+BEGIN_SRC emacs-lisp :tangle no
While the above code enables the =:straight t= extension to =use-package=, let's have that as the default:
#+begin_src emacs-lisp :tangle no
(use-package straight
:custom (straight-use-package-by-default t
straight-default-vc 'git))
#+END_SRC
#+end_src
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
** OS Path and Native Compilation
Helper functions to allow code for specific operating systems:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-running-on-macos? ()
"Return non-nil if running on Mac OS systems."
(equal system-type 'darwin))
@ -68,17 +67,16 @@ Helper functions to allow code for specific operating systems:
(defun ha-running-on-linux? ()
"Return non-nil if running on Linux systems."
(equal system-type 'gnu/linux))
#+END_SRC
#+end_src
With the way I start Emacs, I may not have the PATH I /actually/ use (from the shell) available, so we'll force it (code taken [[https://www.emacswiki.org/emacs/ExecPath][from here]]):
With the way I start Emacs, I may not have the =PATH= I /actually/ use (from the shell) available, so we'll force it (code taken [[https://www.emacswiki.org/emacs/ExecPath][from here]]):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun set-exec-path-from-shell ()
"Set up Emacs' `exec-path' and PATH environment variable to match
that used by the user's shell.
This is particularly useful under Mac OS X and macOS, where GUI
apps are not started from a shell."
The MacOS, where GUI apps are not started from a shell, requires this."
(interactive)
(let* ((path-from-shell (shell-command-to-string "echo $PATH"))
(trimmed-path (replace-regexp-in-string (rx (zero-or-more space) eol)
@ -90,35 +88,35 @@ With the way I start Emacs, I may not have the PATH I /actually/ use (from the s
(message "PATH=%s" path-from-shell)
(setenv "PATH" env-path)
(setq exec-path (split-string trimmed-path separator))))
#+END_SRC
#+end_src
Clear up a Mac-specific issue that sometimes arises since I'm switching to [[http://akrl.sdf.org/gccemacs.html][native compilation project]], as the =Emacs.app= that I use doesn't have its =bin= directory, e.g. =Emacs.app/Contents/MacOS/bin=:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when (ha-running-on-macos?)
(add-to-list 'exec-path "/usr/local/bin")
(add-to-list 'exec-path (concat invocation-directory "bin") t))
#+END_SRC
#+end_src
Getting tired off all the packages that I load spewing a bunch of warnings that I can't do anything about:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when (and (fboundp 'native-comp-available-p)
(native-comp-available-p))
(setq native-comp-async-report-warnings-errors nil
native-comp-deferred-compilation t))
#+END_SRC
#+end_src
** GNU Pretty Good Privacy
On Linux, GPG is pretty straight-forward, but on the Mac, I often have troubles doing:
#+BEGIN_SRC sh
#+begin_src sh
brew install gpg
#+END_SRC
#+end_src
Next, on every reboot, start the agent:
#+BEGIN_SRC sh
#+begin_src sh
/usr/local/Cellar/gnupg/2.3.6/bin/gpg-agent --daemon
#+END_SRC
The only issue is that =brew link gpg= doesnt always work, so maybe a helper function to find the executable:
#+end_src
#+BEGIN_SRC emacs-lisp
Since =brew link gpg= doesnt always work, this helper function may find the executable:
#+begin_src emacs-lisp
(defun executable (path)
"Return PATH if it is executable, see `file-executable-p'."
(let ((epath (first (file-expand-wildcards path))))
@ -131,56 +129,56 @@ The only issue is that =brew link gpg= doesnt always work, so maybe a helper
(executable "/usr/local/opt/gpg")
(executable "/usr/bin/pgp")))
:config (epa-file-enable))
#+END_SRC
#+end_src
** Basic Libraries
The following packages come with Emacs, but seems like they still need loading:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package cl-lib
:straight (:type built-in)
:init (defun first (elt) (car elt))
:commands (first))
(require 'subr-x)
#+END_SRC
#+end_src
Ugh. Why am I getting a missing =first= function error? I define a simple implementation, that the CL library will overwrite ... at some point.
While most libraries will take care of their dependencies, I want to install /my dependent libraries/. Especially, [[https://github.com/magnars/.emacs.d/][Magnar Sveen]]'s Clojure-inspired [[https://github.com/magnars/dash.el][dash.el]] project:
#+BEGIN_SRC emacs-lisp
While most libraries will take care of their dependencies, I want to install /my dependent libraries/, e.g, [[https://github.com/magnars/.emacs.d/][Magnar Sveen]]'s Clojure-inspired [[https://github.com/magnars/dash.el][dash.el]] project:
#+begin_src emacs-lisp
(use-package dash)
#+END_SRC
#+end_src
Sure this package is essentially syntactic sugar, and to help /share/ my configuration, I attempt to use =thread-last= instead of =->>=, but, I still like it.
The [[https://github.com/magnars/s.el][s.el]] project is a simpler string manipulation library that I (and other projects) use:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package s)
#+END_SRC
#+end_src
Manipulate file paths with the [[https://github.com/rejeep/f.el][f.el]] project:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package f)
#+END_SRC
#+end_src
** My Code Location
Much of my more complicated code comes from my website essays and other projects. The destination, however, shows up here:
#+BEGIN_SRC emacs-lisp
Much of my more complicated code comes from my website essays and other projects. The destination shows up here:
#+begin_src emacs-lisp
(add-to-list 'load-path (f-expand "~/.emacs.d/elisp"))
#+END_SRC
#+end_src
Hopefully, this will tie me over while I transition.
** Emacs Server Control
Sure the Emacs application will almost always have the =server-start= going, however, I need to control it just a bit (because I often have two instances running on some of my machines). What /defines/ the Emacs instance for work changes ... often:
Sure the Emacs application will almost always have the =server-start= going, but I need to control it (because I often have two instances running on some of my machines). What /defines/ the Emacs instance for work changes ... often:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-emacs-for-work? ()
"Return non-nil when the Emacs application's location matches as one for work.
Currently, this is the `emacs-plus@28' app that I have built with
This is the `emacs-plus@28' app that I have built with
the native-comp model, but I reserve the right to change this."
(and (f-dir? "~/work")
;; (string-match "emacs-plus@28" exec-directory)
(not (string-match "Emacs.app" exec-directory))))
#+END_SRC
#+end_src
And now start the server with an appropriate tag name:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(if (not (ha-emacs-for-work?))
(setq server-name "personal")
(setq server-name "work")
@ -188,15 +186,16 @@ And now start the server with an appropriate tag name:
(set-exec-path-from-shell)))
(server-start)
#+END_SRC
#+end_src
* Load the Rest
The following loads the rest of my org-mode literate files. I add them as they are /ready/, but eventually, I'll trim this up into a nicer pattern.
#+BEGIN_SRC emacs-lisp
(defvar ha-hamacs-files (flatten-list `("ha-private.org"
"ha-config.org"
The following loads the rest of my org-mode literate files. I add new filesas they are /ready/:
#+begin_src emacs-lisp
(defvar ha-hamacs-files (flatten-list
`("ha-private.org"
"ha-config.org"
,(when (display-graphic-p)
"ha-display.org")
"ha-org.org"
"ha-org.org"
,(when (display-graphic-p)
"ha-org-word-processor.org")
"ha-org-clipboard.org"
@ -217,49 +216,48 @@ The following loads the rest of my org-mode literate files. I add them as they a
"ha-aux-apps.org"
"ha-feed-reader.org"))))
"List of org files that complete the hamacs project.")
#+END_SRC
#+end_src
We can test/debug/reload any individual file, via:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-hamacs-load (file)
"Load or reload an org-mode FILE containing literate Emacs configuration code."
(interactive (list (completing-read "Org file: " ha-hamacs-files)))
(let ((full-file (f-join hamacs-source-dir file)))
(when (f-exists? full-file)
(org-babel-load-file full-file))))
#+END_SRC
#+end_src
And we can now load everything:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-hamacs-reload-all ()
"Reload our entire ecosystem of configuration files."
(interactive)
(dolist (file ha-hamacs-files)
(unless (equal file "bootstrap.org")
(ha-hamacs-load file))))
#+END_SRC
#+end_src
And do it:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-hamacs-reload-all)
#+END_SRC
#+end_src
Once we have loaded /my world/, lets add every other Org file in the project to the list, so that I can easily bring more stuff.
#+BEGIN_SRC emacs-lisp
Once we have loaded /my world/, lets add every other Org file in the project to the list, so that I can load newly created files that I dont want to commit:
#+begin_src emacs-lisp
(setq ha-hamacs-files
(->> (rx ".org" string-end)
(directory-files "~/other/hamacs" nil)
(append ha-hamacs-files)
(--filter (not (string-match (rx "README") it)))
(-uniq)))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
(provide 'bootstrap)
;;; bootstrap.el ends here
#+END_SRC
Let's provide a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'bootstrap)
;;; bootstrap.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~

View file

@ -1,15 +1,14 @@
#+TITLE: Org Agenda Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-18
#+FILETAGS: :emacs:
A literate programming configuration for fancy agenda and todo lists.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-agendas --- Configuration for fancy agenda and todo lists. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,20 +22,20 @@ A literate programming configuration for fancy agenda and todo lists.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
All the code we describe in this file needs loading /after/ org.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(with-eval-after-load "org"
(add-to-list 'org-modules 'org-protocol))
#+END_SRC
#+end_src
* Grouping
Typical agendas have an /order/ to them, but the [[https://github.com/alphapapa/org-super-agenda][org-super-agenda project]] allows you to get specific as well as group them under headings.
Unless you specify otherwise, this is the grouping we'll use:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org-super-agenda
:after org
:init
@ -62,12 +61,12 @@ Unless you specify otherwise, this is the grouping we'll use:
:todo "TODO"
:scheduled future
:order 3))))
#+END_SRC
#+end_src
The task matches a group based on the /code order/, but the =:order= tag allows me to display them in a different order.
The following super agenda is just for /today/ and should be smaller:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(setq ha-org-super-agenda-today
'((:name "Finished"
:todo ("DONE" "CANCELED")
@ -80,13 +79,13 @@ The following super agenda is just for /today/ and should be smaller:
:scheduled past
:date today
:order 0)))
#+END_SRC
#+end_src
* Query Views
The [[https://github.com/alphapapa/org-ql][org-ql project]] gives us a /query language/ of sorts (based on s-expressions).
By putting all queries under =org-ql-views=, we can then call ~M-x query~ and select the view to display:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org-ql
:after org
:config
@ -137,27 +136,27 @@ By putting all queries under =org-ql-views=, we can then call ~M-x query~ and se
:title "Today"
:super-groups 'ha-org-super-agenda-today
:sort '(priority))))))
#+END_SRC
#+end_src
* Agenda Interface
We can create a function to start this:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-todays-agenda ()
"Display an agenda for today, including tasks and scheduled entries."
(interactive)
(org-ql-view "Overview: Today"))
#+END_SRC
#+end_src
And of course, a keybinding:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader "a a" '("my agenda" . ha-todays-agenda))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-agendas)
;;; ha-agendas.el ends here
#+END_SRC
#+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~

View file

@ -1,15 +1,14 @@
#+TITLE: Auxillary and Optional Applications
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-11-18
#+FILETAGS: :emacs:
A literate programming file for helper apps in Emacs.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-aux-apps --- Configuring helper apps in Emacs. -*- lexical-binding: t; -*-
;;
;; © 2021-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,13 +22,13 @@ A literate programming file for helper apps in Emacs.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
The following applications are not really needed. I alternate between trying to /stay in Emacs/ taking advantage of the consistent interface, and simply using a stand-alone app on my Workday computer.
The following applications are not needed. I alternate between trying to /stay in Emacs/ taking advantage of the consistent interface, and using a stand-alone app on my Workday computer.
* Twitter
The venerable [[https://github.com/hayamiz/twittering-mode/tree/master][twittering-mode]] allows me to follow all the twits.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package twittering-mode
:init
(setq twittering-use-master-password t
@ -37,11 +36,11 @@ The venerable [[https://github.com/hayamiz/twittering-mode/tree/master][twitteri
:config
(defalias 'epa--decode-coding-string 'decode-coding-string)
(ha-leader "a t" '("twitter" . twit)))
#+END_SRC
#+end_src
* Telega
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication. Seems to have a bug on the Melpa version, so I'm keeping this to the =HEAD=, but only if I've cloned it.
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication. Seems to have a bug on the Melpa version, so I'm keeping this to the =HEAD=, if I've cloned it.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when (file-exists-p "~/other/telega.el")
(use-package telega
:straight (:local-repo "~/other/telega.el")
@ -50,22 +49,22 @@ I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be
(setq telega-use-images nil)
:config
(ha-leader "a T" 'telega)))
#+END_SRC
#+end_src
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
* RPG DM
Been working on a project for getting Emacs helping as a /Dungeon Master's Assistant/, and I must say, it is coming along nicely. In case you are reading this, let me know, and I'll start to share it.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when (f-directory? "~/other/rpgdm")
(use-package rpgdm
:straight (:local-repo "~/other/rpgdm")
:commands (rpgdm-mode rpgdm-tables-load)
:init (setq rpgdm-base (expand-file-name "~/other/rpgdm"))
:config (ha-leader "t D" '("rpg dm" . rpgdm-mode))))
#+END_SRC
#+end_src
And my new Ironsworn project:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when (f-directory? "~/other/emacs-ironsworn")
(use-package rpgdm-ironsworn
:after rpgdm
@ -76,14 +75,14 @@ And my new Ironsworn project:
org-link-elisp-skip-confirm-regexp (rx string-start (optional "(") "rpgdm-"
(or "tables-" "ironsworn-")
(one-or-more any)))))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-aux-apps)
;;; ha-aux-apps.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming file for helper apps in Emacs.

View file

@ -1,15 +1,14 @@
#+TITLE: Capturing Notes with Org
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-18
#+FILETAGS: :emacs:
A literate programming file for configuring org for capturing notes.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; capturing-notes --- Configuring org for capturing notes. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,188 +22,183 @@ A literate programming file for configuring org for capturing notes.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
Capturing (or collecting) notes from files, browsers, and meetings, is a great way to get organized.
I even have external commands that kick-off the capturing process, and without a command this is what gets called:
#+BEGIN_SRC emacs-lisp
(setq org-capture-default-template "c")
#+END_SRC
#+begin_src emacs-lisp
(setq org-capture-default-template "c")
#+end_src
Let's now define my templates.
* Templates
Just make sure we can execute this code anytime, let's just define the variable that will hold all the templates:
#+BEGIN_SRC emacs-lisp
(defvar org-capture-templates (list))
#+END_SRC
To make sure we can execute this code anytime, let's define the variable that will hold all the templates:
#+begin_src emacs-lisp
(defvar org-capture-templates (list))
#+end_src
Some templates put the information /in front/ of other information (as opposed to the default of appending), so I define a helper function:
#+BEGIN_SRC emacs-lisp
(defun ha-first-header ()
(goto-char (point-min))
(search-forward-regexp "^\* ")
(beginning-of-line 1)
(point))
#+END_SRC
#+begin_src emacs-lisp
(defun ha-first-header ()
(goto-char (point-min))
(search-forward-regexp "^\* ")
(beginning-of-line 1)
(point))
#+end_src
** General Notes
Capturing text into the =org-default-notes-file= is something I don't do much:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
'("n" "Thought or Note" entry
(file org-default-notes-file)
"* %?\n\n %i\n\n See: %a" :empty-lines 1))
(add-to-list 'org-capture-templates
'("w" "Website Announcement" entry
(file+function "~/website/index.org" ha-first-header)
(file "~/.spacemacs.d/templates/website-announcement.org")
:empty-lines 1))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
'("n" "Thought or Note" entry
(file org-default-notes-file)
"* %?\n\n %i\n\n See: %a" :empty-lines 1))
(add-to-list 'org-capture-templates
'("w" "Website Announcement" entry
(file+function "~/website/index.org" ha-first-header)
(file "~/.spacemacs.d/templates/website-announcement.org")
:empty-lines 1))
#+end_src
Before we go too far, we should create a publishing file for the website announcement, and something for the journal.
** Clock in Tasks
Org has one task at a time that can be /clocked in/ keeping a timer. I use that as a /destination/ for collecting notes. For instance, capturing with a =c= allows me to just enter stuff in under that task without switching to it:
Org has one task at a time that can be /clocked in/ keeping a timer. I use that as a /destination/ for collecting notes. For instance, capturing with a =c= allows me to just enter details under that task without switching to it:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
'("c" "Currently clocked in task"))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
'("c" "Currently clocked in task"))
#+end_src
Let's put a bullet item under that task:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
`("cc" "Item to Current Clocked Task" item
(clock)
"%i%?" :empty-lines 1))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
`("cc" "Item to Current Clocked Task" item
(clock)
"%i%?" :empty-lines 1))
#+end_src
We can select a /region/ and copy that using =c r=:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
`("cr" "Contents to Current Clocked Task" plain
(clock)
"%i" :immediate-finish t :empty-lines 1))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
`("cr" "Contents to Current Clocked Task" plain
(clock)
"%i" :immediate-finish t :empty-lines 1))
#+end_src
If we have copied anything into the clipboard, that information can be add to the current task using =c k=:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
`("ck" "Kill-ring to Current Clocked Task" plain
(clock)
"%c" :immediate-finish t :empty-lines 1))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
`("ck" "Kill-ring to Current Clocked Task" plain
(clock)
"%c" :immediate-finish t :empty-lines 1))
#+end_src
Instead, if I am looking at some code, I can copy some code from a region, but use a helper function to create a /link/ to the original source code using =c f=:
#+BEGIN_SRC emacs-lisp
(add-to-list 'org-capture-templates
`("cf" "Code Reference with Comments to Current Task"
plain (clock)
"%(ha-org-capture-code-snippet \"%F\")\n\n %?"
:empty-lines 1))
#+END_SRC
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
`("cf" "Code Reference with Comments to Current Task"
plain (clock)
"%(ha-org-capture-code-snippet \"%F\")\n\n %?"
:empty-lines 1))
#+end_src
If I want a reference to the code, without any comments, I call ~c l~:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(add-to-list 'org-capture-templates
`("cl" "Link to Code Reference to Current Task"
plain (clock)
"%(ha-org-capture-code-snippet \"%F\")"
:empty-lines 1 :immediate-finish t))
#+END_SRC
#+end_src
** Capture Helper Functions
In order to have a capture back-ref to a function and its code, we need to use this:
#+BEGIN_SRC emacs-lisp
(require 'which-func)
#+END_SRC
To have a capture back-ref to a function and its code, we need to use this:
#+begin_src emacs-lisp
(require 'which-func)
#+end_src
This helper function given a code /type/ and the /function/, analyzes the current buffer in order to collects data about the source code file. It then creates a nice-looking template:
#+BEGIN_SRC emacs-lisp
(defun ha-org-capture-fileref-snippet (f type headers func-name)
(let* ((code-snippet
(buffer-substring-no-properties (mark) (- (point) 1)))
(file-name (buffer-file-name))
(file-base (file-name-nondirectory file-name))
(line-number (line-number-at-pos (region-beginning)))
(initial-txt (if (null func-name)
(format "From [[file:%s::%s][%s]]:"
file-name line-number file-base)
(format "From ~%s~ (in [[file:%s::%s][%s]]):"
func-name file-name line-number
file-base))))
(format "
%s
#+begin_src emacs-lisp
(defun ha-org-capture-fileref-snippet (f type headers func-name)
(let* ((code-snippet
(buffer-substring-no-properties (mark) (- (point) 1)))
(file-name (buffer-file-name))
(file-base (file-name-nondirectory file-name))
(line-number (line-number-at-pos (region-beginning)))
(initial-txt (if (null func-name)
(format "From [[file:%s::%s][%s]]:"
file-name line-number file-base)
(format "From ~%s~ (in [[file:%s::%s][%s]]):"
func-name file-name line-number
file-base))))
(format "
%s
#+BEGIN_%s %s
%s
#+END_%s" initial-txt type headers code-snippet type)))
#+END_SRC
,#+BEGIN_%s %s
%s
,#+END_%s" initial-txt type headers code-snippet type)))
#+end_src
For typical code references, we can get the label for Org's =SRC= block by taking the =major-mode= and removing the =-mode= part. We can then call the formatter we previously defined:
For typical code references, we can get the label for Org's =SRC= block by taking the =major-mode= and removing the =-mode= part. We can then call the formatter previously defined:
#+BEGIN_SRC emacs-lisp
(defun ha-org-capture-code-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org SRC block with a language based on the current mode
and a backlink to the function and the file."
(with-current-buffer (find-buffer-visiting f)
(let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
(func-name (which-function)))
(ha-org-capture-fileref-snippet f "SRC" org-src-mode func-name))))
#+END_SRC
#+begin_src emacs-lisp
(defun ha-org-capture-code-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org SRC block with a language based on the current mode
and a backlink to the function and the file."
(with-current-buffer (find-buffer-visiting f)
(let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
(func-name (which-function)))
(ha-org-capture-fileref-snippet f "SRC" org-src-mode func-name))))
#+end_src
Let's assume that we want to copy some text from a file, but it isn't source code, then this function makes an =EXAMPLE= of it.
#+BEGIN_SRC emacs-lisp
(defun ha-org-capture-clip-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org EXAMPLE block and a backlink to the file."
(with-current-buffer (find-buffer-visiting f)
(ha-org-capture-fileref-snippet f "EXAMPLE" "" nil)))
#+END_SRC
#+begin_src emacs-lisp
(defun ha-org-capture-clip-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org EXAMPLE block and a backlink to the file."
(with-current-buffer (find-buffer-visiting f)
(ha-org-capture-fileref-snippet f "EXAMPLE" "" nil)))
#+end_src
** Code Capturing Functions
In order to easily call a capture for code, let's make two interactive functions, one just copies the stuff, and the other pulls up a capturing window for comments:
To easily call a capture for code, let's make two interactive functions, one just copies the stuff, and the other pulls up a capturing window for comments:
#+BEGIN_SRC emacs-lisp
(defun ha-code-to-clock (&optional start end)
"Send the currently selected code to the currently clocked-in org-mode task."
(interactive)
(org-capture nil "F"))
#+begin_src emacs-lisp
(defun ha-code-to-clock (&optional start end)
"Send the currently selected code to the currently clocked-in org-mode task."
(interactive)
(org-capture nil "F"))
(defun ha-code-comment-to-clock (&optional start end)
"Send the currently selected code (with comments) to the
currently clocked-in org-mode task."
(interactive)
(org-capture nil "f"))
#+END_SRC
(defun ha-code-comment-to-clock (&optional start end)
"Send the currently selected code (with comments) to the
currently clocked-in org-mode task."
(interactive)
(org-capture nil "f"))
#+end_src
* External Capturing
If we put something on the clipboard using =xclip= or something, and then
perhaps =emacsclient= could call this function to put those contents into clocked in task.
#+BEGIN_SRC emacs-lisp
(defun ha-external-capture-to-org ()
"Calls `org-capture-string' on the contents of the Apple clipboard."
(interactive)
(org-capture-string (ha-org-clipboard) "ck")
(ignore-errors
(delete-frame)))
#+END_SRC
#+begin_src emacs-lisp
(defun ha-external-capture-to-org ()
"Calls `org-capture-string' on the contents of the Apple clipboard."
(interactive)
(org-capture-string (ha-org-clipboard) "ck")
(ignore-errors
(delete-frame)))
#+end_src
The =en= script is used as the last pipe entry on the command line, this displays the output, and then copies the contents into the Emacs-based engineering notebook at the currently clocked in task.
#+BEGIN_SRC shell :shebang "#!/bin/bash" :tangle ~/bin/en
#+begin_src shell :shebang "#!/bin/bash" :tangle ~/bin/en
# Interface to my Engineering Notebook.
#
# Used as the last pipe entry on the command line, this displays the output,
@ -292,10 +286,10 @@ The =en= script is used as the last pipe entry on the command line, this display
emacsclient -s work -e '(org-capture-string "" "ck")'
rm -f $FILE
#+END_SRC
#+end_src
* Keybindings
Along with kicking off the org-capture, I want to be able to clock-in and out:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(with-eval-after-load 'ha-org
(ha-org-leader
"X" '("org capture" . org-capture)
@ -313,15 +307,15 @@ Along with kicking off the org-capture, I want to be able to clock-in and out:
"c t" '("eval range" . org-evaluate-time-range)
"c =" '("timestamp up" . org-clock-timestamps-up)
"c -" '("timestamp down" . org-clock-timestamps-down)))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so we can =require= this file.
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-capturing-notes)
;;; ha-capturing-notes.el ends here
#+END_SRC
#+begin_src emacs-lisp :exports none
(provide 'ha-capturing-notes)
;;; ha-capturing-notes.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~

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,14 @@
#+TITLE: Emacs Graphical Display Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-10
#+FILETAGS: :emacs:
A literate programming file to configure the Emacs UI.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-display --- Emacs UI configuration. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,20 +22,20 @@ A literate programming file to configure the Emacs UI.
;; Using `find-file-at-point', and tangle the file to recreate this one .
;;
;;; Code:
#+END_SRC
#+end_src
* Dashboard
The [[https://github.com/emacs-dashboard/emacs-dashboard][emacs-dashboard]] project makes a nicer startup screen. It requires [[https://github.com/purcell/page-break-lines][page-break-lines]] (which is a nice project):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package page-break-lines)
#+END_SRC
#+end_src
And lets make this Emacs look more like a fancy IDE with [[https://github.com/domtronn/all-the-icons.el][all-the-icons]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package all-the-icons
:if (display-graphic-p))
#+END_SRC
#+end_src
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package dashboard
:init
(defun ha-dashboard-version ()
@ -61,10 +60,10 @@ And lets make this Emacs look more like a fancy IDE with [[https://github.com
(dashboard-setup-startup-hook)
(setq dashboard-footer-messages (list (ha--dad-joke))))
#+END_SRC
#+end_src
I would appreciate seeing if my Emacs installation has the features that I expect:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-hamacs-features (&optional iconic)
"Simple display of features I'm most keen about.
If ICONIC is non-nil, return a string of icons."
@ -86,10 +85,10 @@ I would appreciate seeing if my Emacs installation has the features that I expec
(if (called-interactively-p)
(message "Enabled features: %s" results)
results)))
#+END_SRC
#+end_src
* Mode Line
Let's install and load some of packages from the [[https://github.com/hlissner/doom-emacs][Doom Emacs]] project, like [[https://github.com/seagle0128/doom-modeline][doom-modeline]] and maybe the themes:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package doom-modeline
:init
(setq doom-modeline-minor-modes nil
@ -99,11 +98,11 @@ Let's install and load some of packages from the [[https://github.com/hlissner/d
(doom-modeline-mode +1))
(use-package doom-themes)
#+END_SRC
#+end_src
* Find the Bloody Cursor
Large screen, lots of windows, so where is the cursor? While I used to use =hl-line+=, I found that the prolific [[https://protesilaos.com/][Protesilaos Stavrou]] [[https://protesilaos.com/codelog/2022-03-14-emacs-pulsar-demo/][introduced his Pulsar project]] is just what I need. Specifically, I might /loose the cursor/ and need to have it highlighted (using ~F6~), but also, this automatically highlights the cursor line with specific /actions/ , like changing windows.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package pulsar
:straight (:type git :protocol ssh :host gitlab :repo "protesilaos/pulsar")
:custom
@ -152,52 +151,52 @@ Large screen, lots of windows, so where is the cursor? While I used to use =hl-l
(pulsar-face 'pulsar-magenta)
(pulsar-delay 0.055)
:bind ("<f6>" . pulsar-pulse-line))
#+END_SRC
#+end_src
* Themes
One does get used to a particular collection of colors. Mine is Tomorrow:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package color-theme-sanityinc-tomorrow)
#+END_SRC
#+end_src
Most of the time, Emacs is on my desk is a darkened room, so I choose the dark theme:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun laptop-inside ()
(interactive)
(load-theme 'sanityinc-tomorrow-night t)
(set-face-attribute 'region nil :background "#000096")
(set-face-attribute 'mode-line nil :background "black")
(set-face-attribute 'mode-line-inactive nil :background "#333333"))
#+END_SRC
#+end_src
But, when feeling adventurous, I /sometimes/ take my laptop outside:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun laptop-in-the-sun ()
(interactive)
(load-theme 'sanityinc-tomorrow-day t)
(set-face-attribute 'region nil :background "orange1")
(set-face-attribute 'mode-line nil :background "#cccccc")
(set-face-attribute 'mode-line-inactive nil :background "#888888"))
#+END_SRC
#+end_src
Oh, and turn off the line highlighting:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(global-hl-line-mode -1)
#+END_SRC
#+end_src
And of course, the default is /inside/ where it is dark and safe:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(laptop-inside)
#+END_SRC
#+end_src
* Full Size Frame
Taken from [[https://emacsredux.com/blog/2020/12/04/maximize-the-emacs-frame-on-startup/][this essay]], I figured I would start the initial frame automatically in fullscreen, but not any subsequent frames (as this could be part of the capturing system).
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(add-to-list 'initial-frame-alist '(fullscreen . maximized))
#+END_SRC
#+end_src
* Font Configuration
Am I ever really ever satisfied with any font? I regularly change my font based on the monospace du jour... [[http://blogs.adobe.com/typblography/2012/09/source-code-pro.html][Source Code Pro]] is attractive, and has been a staple on every programmers' screen. However, we all want ligatures, [[https://github.com/i-tu/Hasklig][Hasklig]] is a nice font that is thinner and easier to read than [[https://github.com/tonsky/FiraCode][Fira]], but [[https://typeof.net/Iosevka/][Iosevka]] seems to have it all. Oh, Microsoft just gave us [[https://docs.microsoft.com/en-us/windows/terminal/cascadia-code][Cascadia]] and that seems shiny. However, the [[https://github.com/ryanoasis/nerd-fonts][Nerd Font project]] adds the ligatures as well as all the other niceties to a font.
@ -226,7 +225,7 @@ I stole the following idea from [[https://protesilaos.com/dotemacs/#h:9035a1ed-e
The following is from [[https://source-foundry.github.io/Hack/font-specimen.html][Hack's website]]:
#+BEGIN_SRC c
#+begin_src c
// The four boxing wizards jump
#include <stdio.h> // <= quickly.
int main(int argc, char **argv) {
@ -236,16 +235,16 @@ int main(int argc, char **argv) {
printf("@$Hamburgefo%c`",'\n');
return ~7&8^9?0:l1|!"j->k+=*w";
}
#+END_SRC
#+end_src
To install a font, I use the following command on my Mac:
#+BEGIN_SRC sh
#+begin_src sh
brew tap homebrew/cask-fonts
brew install --cask font-hack-nerd-font
#+END_SRC
#+end_src
** Specifying a Font
My /current/ favorite font is actually the top list of fonts that may be installed on my system (they usually are):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defvar ha-fixed-font
(when window-system
(cond
@ -261,15 +260,15 @@ My /current/ favorite font is actually the top list of fonts that may be install
((x-list-fonts "Anonymous Pro") "Anonymous Pro")
(t "monospaced")))
"My fixed width font based on what is installed, `nil' if not defined.")
#+END_SRC
#+end_src
Force something as well:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(setq ha-fixed-font "Hack Nerd Font")
#+END_SRC
#+end_src
I probably don't need to have such a ranking system, as chances are really good that I'll have all of them installed. Still.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defvar ha-variable-font
(when window-system
(cond ((x-list-fonts "Overpass") "Overpass")
@ -279,22 +278,22 @@ I probably don't need to have such a ranking system, as chances are really good
((x-family-fonts "Sans Serif") "Sans Serif")
(nil (warn "Cannot find a Sans Serif Font. Install Source Sans Pro."))))
"My variable width font available to org-mode files and whatnot.")
#+END_SRC
#+end_src
Simple function that gives me the font information based on the size I need. Recently updated after reading [[https://protesilaos.com/codelog/2020-09-05-emacs-note-mixed-font-heights/][this essay]], as I wanted my =fixed-pitch= to scale along with my =variable-pitch= font.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-set-favorite-font-size (size)
"Set the default font size as well as equalize the fixed and variable fonts."
(let ((fav-font (format "%s-%d" ha-fixed-font size)))
(set-face-attribute 'default nil :font fav-font)
(set-face-attribute 'fixed-pitch nil :family ha-fixed-font :inherit 'default :height 1.0)
(set-face-attribute 'variable-pitch nil :family ha-variable-font :inherit 'default :height 1.2)))
#+END_SRC
#+end_src
Define /interactive/ functions to quickly adjusting the font size based on my computing scenario:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-mac-monitor-fontsize ()
"Quickly set reset my font size when I connect my laptop to a monitor on a Mac."
(interactive)
@ -319,11 +318,11 @@ Define /interactive/ functions to quickly adjusting the font size based on my co
"Quickly set reset my font size when I am on my iMac."
(interactive)
(ha-set-favorite-font-size 16))
#+END_SRC
#+end_src
Which font to choose?
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun font-monitor-size-default ()
"Set the default size according to my preference."
(interactive)
@ -340,12 +339,12 @@ Which font to choose?
(ha-mac-laptop-fontsize)))
(font-monitor-size-default)
#+END_SRC
#+end_src
** Zooming or Increasing Font Size
Do we want to increase the size of font in a single window (using =text-scale-increase=), or globally (using my new =font-size-increase=)?
Increase or decrease the set size of the face:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun font-size-adjust (delta)
"Adjust the current frame's font size.
DELTA would be something like 1 or -1."
@ -366,25 +365,25 @@ Increase or decrease the set size of the face:
"Decrease the `default' font size of all frames."
(interactive)
(font-size-adjust -1))
#+END_SRC
#+end_src
And some keybindings to call them:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(global-set-key (kbd "s-+") 'font-size-increase)
(global-set-key (kbd "s-=") 'font-size-increase)
(global-set-key (kbd "s--") 'font-size-decrease)
(global-set-key (kbd "s-0") 'font-size-monitor-default)
(global-set-key (kbd "s-9") 'font-size-laptop-default)
#+END_SRC
#+end_src
* Emojis, Icons and Whatnot
Sometimes two symbols should really be one, for instance:
#+BEGIN_SRC emacs-lisp
Display these two symbols as one:
#+begin_src emacs-lisp
(add-hook 'text-mode-hook (lambda ()
(push '("!?" . "‽") prettify-symbols-alist)))
#+END_SRC
#+end_src
In Emacs 28.1, we have better Unicode 14 support. Which means, we need to install [[https://github.com/googlefonts/noto-emoji][Noto Color Emoji]]. Currently, my systems, seems to work just fine, but Im leaving this code here in case I have issues, as I might use what Apple supplies when on a Mac (thanks [[http://xahlee.info/emacs/emacs/emacs_list_and_set_font.html][Xah Lee]]):
#+BEGIN_SRC emacs-lisp :tangle no
In Emacs 28.1, we have better Unicode 14 support. Which means, we need to install [[https://github.com/googlefonts/noto-emoji][Noto Color Emoji]]. My systems, seems to work fine, but Im leaving this code here in case I have issues, as I might use what Apple supplies when on a Mac (thanks [[http://xahlee.info/emacs/emacs/emacs_list_and_set_font.html][Xah Lee]]):
#+begin_src emacs-lisp :tangle no
;; set font for symbols
(set-fontset-font t 'symbol
(cond
@ -401,36 +400,35 @@ In Emacs 28.1, we have better Unicode 14 support. Which means, we need to instal
((member "Apple Color Emoji" (font-family-list)) "Apple Color Emoji")
((member "Noto Color Emoji" (font-family-list)) "Noto Color Emoji")
((member "Symbola" (font-family-list)) "Symbola")))
#+END_SRC
#+end_src
Test this out: 😄 😱 😸 👸 👽 🙋
Not use what I'm doing with the [[https://github.com/domtronn/all-the-icons.el][all-the-icons]] package, but the Doom Modeline uses much of this.
#+BEGIN_SRC emacs-lisp
(use-package all-the-icons)
#+END_SRC
#+begin_src emacs-lisp
(use-package all-the-icons)
#+end_src
*Note:* Install everything with the function, =all-the-icons-install-fonts=.
* Ligatures
Seems like getting ligatures to work in Emacs has been a Holy Grail. On Mac, I've used special builds that have hacks, but now with Emacs 27 and Harfbuzz, I should be able to get --> to look like it should.
#+BEGIN_SRC emacs-lisp :tangle no
(setq prettify-symbols-unprettify-at-point 'right-edge)
#+begin_src emacs-lisp :tangle no
(setq prettify-symbols-unprettify-at-point 'right-edge)
(global-prettify-symbols-mode +1)
(prettify-symbols-mode +1)
#+END_SRC
(global-prettify-symbols-mode +1)
(prettify-symbols-mode +1)
#+end_src
Note, in Doom, is appears we have a =ligatures= module.
We'll start using that instead, but changing it in [[file:general-programming.org][general-programming]] file.
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-display)
;;; ha-display.el ends here
#+END_SRC
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-display)
;;; ha-display.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~

View file

@ -1,15 +1,14 @@
#+TITLE: Configuring Emacs for Email with Notmuch
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-16
#+FILETAGS: :emacs:
A literate configuration file for email using Notmuch.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-email --- Email configuration using Notmuch. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,36 +22,36 @@ A literate configuration file for email using Notmuch.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
To use this system, begin with ~SPC a m~ (after a ~SPC a n~ to asychronously download new mail ... which probably should be running regularly).
To use this system, begin with ~SPC a m~ (after a ~SPC a n~ to asychronously download new mail … which probably should be running beforehand).
When the Notmuch interface up, hit ~J~ to jump to one of the Search boxes (described below). Typically, this is ~i~ for the Imbox, check out the focused message from people I care). Hit ~q~ to return.
Next type ~s~ to view and organize mail I've never seen before. We need to keep things focused, so regularly /making auto filtering rules/ is important. Move the point to the message and hit one of the following to automatically move the sender to a pre-defined box:
Next type ~s~ to view and organize mail I've never seen before. We need to focus the display, so /creating auto filtering rules/ is important. Move the point to the message and hit one of the following to automatically move the sender to a pre-defined box:
- ~I~ :: screened stuff that important enough to go to my Imbox
- ~S~ :: spam ... so much goes here
- ~I~ :: screened email important enough to go to my Imbox
- ~S~ :: spam so much goes here
- ~P~ :: receipts go to this *Paper Trail*, which takes a *tag* as the name of the store
- ~f~ :: mailing lists and other things that might be nice to read go to *The Feed*
- ~f~ :: mailing lists and other email that might be nice to read go to *The Feed*
** Email Addresses
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /hardly/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /not/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
#+NAME: email-address-1
#+BEGIN_SRC emacs-lisp :exports none :tangle no :results silent
#+begin_src emacs-lisp :exports none :tangle no :results silent
(rot13-string "ubjneq.noenzf@tznvy.pbz")
#+END_SRC
#+end_src
#+NAME: email-address-2
#+BEGIN_SRC emacs-lisp :exports none :tangle no :results silent
#+begin_src emacs-lisp :exports none :tangle no :results silent
(rot13-string "ubjneq@ubjneqnoenzf.pbz")
#+END_SRC
#+end_src
#+NAME: email-address-3
#+BEGIN_SRC emacs-lisp :exports none :tangle no :results silent
#+begin_src emacs-lisp :exports none :tangle no :results silent
(rot13-string "ubjneq@shmmlgbnfg.pbz")
#+END_SRC
#+end_src
To use these, we set the =:noweb yes= (to pull in the /name/ of the code block) but put a pair of parens after the name to have it evaluated. For instance:
#+begin_example
@ -65,19 +64,19 @@ some-address: <<email-address-2()>>
To begin, we need the code. On Ubuntu, this is:
#+BEGIN_SRC shell :tangle no
#+begin_src shell :tangle no
sudo apt install -y notmuch
#+END_SRC
#+end_src
And on MacOS, we use =brew=:
#+BEGIN_SRC shell :tangle no
#+begin_src shell :tangle no
brew install notmuch
#+END_SRC
#+end_src
Next, we need some basic configuration settings and some global keybindings:
#+BEGIN_SRC emacs-lisp :noweb yes
#+begin_src emacs-lisp :noweb yes
(use-package notmuch
:init
(setq mail-user-agent 'notmuch-user-agent
@ -109,26 +108,26 @@ Next, we need some basic configuration settings and some global keybindings:
"C" '("reply-later" . hey-notmuch-reply-later))
<<hey-show-keybindings>>
<<hey-search-keybindings>>)
#+END_SRC
#+end_src
Also, let's do some basic configuration of Emacs' mail system:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq mm-text-html-renderer 'shr
mail-specify-envelope-from t
message-kill-buffer-on-exit t
message-send-mail-function 'message-send-mail-with-sendmail
message-sendmail-envelope-from 'header)
#+END_SRC
#+end_src
* Configuration
Do I want to sign messages by default? Nope.
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(add-hook 'message-setup-hook 'mml-secure-sign-pgpmime)
#+END_SRC
#+end_src
** Addresses
I need to incorporate an address book again, but in the meantime, searching through a history of past email works well enough.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq notmuch-address-selection-function
(lambda (prompt collection initial-input)
(completing-read prompt
@ -137,7 +136,7 @@ I need to incorporate an address book again, but in the meantime, searching thro
t
nil
'notmuch-address-history)))
#+END_SRC
#+end_src
** Sending Messages
Do I need to set up [[https://marlam.de/msmtp/][MSMTP]]? No, as Notmuch will do that work.
@ -147,19 +146,18 @@ To do this, type ~c~ and select an option (including ~r~ to reply).
When we start notmuch, we need to retrieve the email and then process it. Most of this is actually contained in the Notmuch configuration.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun notmuch-retrieve-messages ()
"Retrieve and process my mail messages."
(interactive)
(async-shell-command "notmuch new"))
#+END_SRC
#+end_src
* iSync Configuration
Using [[https://isync.sourceforge.io/][isync]] (or is it =mbsync=) for mail retrieval.
Currently, I have a couple of Google Mail accounts that I want connected.
Using [[https://isync.sourceforge.io/][isync]] (or is it =mbsync=) for mail retrieval. I have a couple of Google Mail accounts that I want connected.
The file generally can have a =Pass= entry for the encrypted passcode, but in order to demonstrate how to connect to multiple accounts, I'm using a GPG daemon:
The file generally can have a =Pass= entry for the encrypted passcode, but to show how to connect to more than one accounts, I'm using a GPG daemon:
#+BEGIN_SRC conf :tangle ~/.mbsyncrc :noweb yes
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
# Note: We now tangle this file from ~/other/hamacs/ha-email.org
Create Both
SyncState *
@ -169,7 +167,7 @@ The file generally can have a =Pass= entry for the encrypted passcode, but in or
# PERSONAL ACCOUNT
IMAPAccount personal
Host imap.gmail.com
User <<email-address-2()>> # Obviously, you'd substitute your own email address here
User <<email-address-2()>> # Substitute your own email address here
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.mailpass-personal.gpg"
SSLType IMAPS
AuthMechs LOGIN
@ -232,67 +230,63 @@ The file generally can have a =Pass= entry for the encrypted passcode, but in or
Master :gmail-remote:"[Gmail]/Trash"
Slave :gmail-local:trash
ExpireUnread yes
#+END_SRC
#+end_src
* Notmuch Configuration
Notmuch requires a few configuration files.
Notmuch requires these configuration files.
** =notmuch-config=
The general settings file that goes into =~/.notmuch-config=:
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config
#+begin_src conf-unix :tangle ~/.notmuch-config
# .notmuch-config - Configuration file for the notmuch mail system
# Note: We now tangle this file from ~/other/hamacs/ha-email.org
#
# For more information about notmuch, see https://notmuchmail.org
#+END_SRC
#+end_src
The commentary for each of the subsections came from their man page.
*** Database configuration
The only value supported here is 'path' which should be the top-level directory where your mail currently exists and to where mail will be delivered in the future. Files should be individual email messages. Notmuch will store its database within a sub-directory of the path configured here named ".notmuch".
The value supported here is =path= which should be the top-level directory where your mail exists and to where =mbsync= will new mail. Files should be individual email messages. Notmuch will store its database within a sub-directory of the path configured here named ".notmuch".
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config
#+begin_src conf-unix :tangle ~/.notmuch-config
[database]
path=.mail
#+END_SRC
#+end_src
*** User configuration
Here is where you can let notmuch know how you would like to be addressed. Valid settings are
Here is where you can let notmuch know how you address emails. Valid settings are
- =name= :: Your full name.
- =primary_email= :: Your primary email address.
- =other_email= :: A list (separated by =;=) of other email addresses at which you receive email.
Notmuch will use the various email addresses configured here when formatting replies. It will avoid including your own addresses in the recipient list of replies, and will set the From address based on the address to which the original email was addressed.
Notmuch use the email addresses configured here when formatting replies. It will avoid including your own addresses in the recipient list of replies, and will set the From address based on the address in the original email.
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config :noweb yes
#+begin_src conf-unix :tangle ~/.notmuch-config :noweb yes
[user]
name=Howard Abrams
primary_email=<<email-address-1()>>
other_email=<<email-address-2()>>;<<email-address-3()>>
#+END_SRC
*NB:* In the configuration above, you may see the addresses are all set to =nil=. If you are copying this from a rendered web page, just note that you need to substitute that with your own email address.
#+end_src
*NB:* In the configuration above, you may see the addresses are all set to =nil=. If you are copying this from a rendered web page, note that you need to substitute that with your own email address.
*** Configuration for "notmuch new"
The following options are supported here:
- =tags= :: A list (separated by =;=) of the tags that will be added to all messages incorporated by "notmuch new".
Note the following supported options:
- =tags= :: A list (separated by =;=) of the tags that added to all messages incorporated by "notmuch new".
- =ignore= :: A list (separated by =;=) of file and directory names that will not be searched for messages by "notmuch new".
NOTE: *Every* file/directory that goes by one of those names will be ignored, independent of its depth/location in the mail store.
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config
#+begin_src conf-unix :tangle ~/.notmuch-config
[new]
tags=unread;inbox;
ignore=
#+END_SRC
#+end_src
*** Search configuration
The following option is supported here:
- =exclude_tags= :: A ;-separated list of tags that will be excluded from search results by default. Using an excluded tag in a query will override that exclusion.
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config
#+begin_src conf-unix :tangle ~/.notmuch-config
[search]
exclude_tags=deleted;spam;
#+END_SRC
#+end_src
*** Maildir compatibility configuration
The following option is supported here:
@ -308,16 +302,16 @@ The following option is supported here:
The =notmuch new= command will notice flag changes in filenames and update tags, while the =notmuch tag= and =notmuch restore= commands will notice tag changes and update flags in filenames.
#+BEGIN_SRC conf-unix :tangle ~/.notmuch-config
#+begin_src conf-unix :tangle ~/.notmuch-config
[maildir]
synchronize_flags=true
#+END_SRC
#+end_src
That should complete the Notmuch configuration.
** =pre-new=
Then we need a shell script called when beginning a retrieval, =pre-new= that simply calls =mbsync= to download all the messages:
#+BEGIN_SRC shell :tangle ~/.mail/.notmuch/hooks/pre-new :shebang "#!/bin/bash"
#+begin_src shell :tangle ~/.mail/.notmuch/hooks/pre-new :shebang "#!/bin/bash"
# More info about hooks: https://notmuchmail.org/manpages/notmuch-hooks-5/
# Note: We now tangle this file from ~/other/hamacs/ha-email.org
@ -326,11 +320,11 @@ echo "Starting not-much 'pre-new' script"
mbsync -a
echo "Completing not-much 'pre-new' script"
#+END_SRC
#+end_src
** =post-new=
And a =post-new= hook based on a filtering scheme that mimics the Hey.com workflow taken from [[https://gist.githubusercontent.com/frozencemetery/5042526/raw/57195ba748e336de80c27519fe66e428e5003ab8/post-new][this gist]] (note we have more to say on that later on) to filter and tag all messages after they have arrived:
#+BEGIN_SRC shell :tangle ~/.mail/.notmuch/hooks/post-new :shebang "#!/bin/bash"
#+begin_src shell :tangle ~/.mail/.notmuch/hooks/post-new :shebang "#!/bin/bash"
# Based On: https://gist.githubusercontent.com/frozencemetery/5042526/raw/57195ba748e336de80c27519fe66e428e5003ab8/post-new
# Note: We now tangle this file from ~/other/hamacs/ha-email.org
#
@ -422,7 +416,7 @@ timer_end "Old-Projects"
notmuch tag +screened 'subject:[Web]'
echo "Completing not-much 'post-new' script"
#+END_SRC
#+end_src
* Hey
I originally took the following configuration from [[https://youtu.be/wuSPssykPtE][Vedang Manerikar's video]], along with [[https://gist.github.com/vedang/26a94c459c46e45bc3a9ec935457c80f][the code]]. The ideas brought out were to mimic the hey.com email workflow, and while not bad, I thought that maybe I could improve upon it slowly over time.
@ -431,7 +425,7 @@ To allow me to keep Vedang's and my code side-by-side in the same Emacs variable
A list of pre-defined searches act like "Folder buttons" at the top to quickly see files that match those /buckets/:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq notmuch-saved-searches '((:name "Imbox"
:query "tag:inbox AND tag:screened AND tag:unread"
:key "i"
@ -464,12 +458,12 @@ A list of pre-defined searches act like "Folder buttons" at the top to quickly s
(:name "Old Projects"
:query "tag:old-project AND NOT tag:unread"
:key "X")))
#+END_SRC
#+end_src
** Helper Functions
With good bucket definitions, we should be able to scan the mail quickly and deal with the entire lot of them:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun hey-notmuch-archive-all ()
"Archive all the emails in the current view."
(interactive)
@ -495,11 +489,11 @@ When called directly, BEG and END provide the region."
(interactive (notmuch-search-interactive-tag-changes))
(notmuch-search-tag tag-changes beg end)
(notmuch-search-archive-thread nil beg end))
#+END_SRC
#+end_src
A key point in organizing emails with the Hey model, is looking at the "from" address:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun hey-notmuch-search-find-from ()
"A helper function to find the email address for the given email."
(let ((notmuch-addr-sexp (first
@ -509,11 +503,11 @@ A key point in organizing emails with the Hey model, is looking at the "from" ad
"--output=sender"
(notmuch-search-find-thread-id)))))
(plist-get notmuch-addr-sexp :address)))
#+END_SRC
#+end_src
And we can create a filter, /search/ and tagging based on this "from" function:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun hey-notmuch-filter-by-from ()
"Filter the current search view to show all emails sent from the sender of the current thread."
(interactive)
@ -546,13 +540,13 @@ search."
(when refresh
(set-buffer this-buf)
(notmuch-refresh-this-buffer))))
#+END_SRC
#+end_src
** Moving Mail to Buckets
We based the Hey buckets on notmuch databases, we combine the =hey-notmuch-add-addr-to-db= with the =hey-notmuch-tag-by-from= functions to move messages.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun hey-notmuch-add-addr-to-db (nmaddr nmdbfile)
"Add the email address NMADDR to the db-file NMDBFILE."
(append-to-file (format "%s\n" nmaddr) nil nmdbfile))
@ -626,12 +620,12 @@ This means:
(substring (notmuch-show-get-from) 0 15)))
(email-string (format "%s (From: %s)" email-subject email-from)))
(message "Noted! Reply Later: %s" email-string)))
#+END_SRC
#+end_src
** Bucket Keybindings
In /Emacs/ mode, we can just call =define-key=, but since it starts in Evil state (and we may want to use Evil keybindings, let's create some local leaders:
#+NAME: local-leader-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(general-create-definer ha-mail-hello-leader
:states '(normal visual motion)
:keymaps 'notmuch-hello-mode-map
@ -652,23 +646,23 @@ In /Emacs/ mode, we can just call =define-key=, but since it starts in Evil stat
:prefix "SPC m"
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+END_SRC
#+end_src
A series of keybindings to quickly send messages to one of the pre-defined buckets.
#+NAME: hey-show-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(ha-mail-show-leader
"c" '("compose" . notmuch-mua-new-mail)
"C" '("reply-later" . hey-notmuch-reply-later))
(define-key notmuch-show-mode-map (kbd "C") 'hey-notmuch-reply-later)
#+END_SRC
#+end_src
The bindings in =notmuch-search-mode= are available when looking at a list of messages:
#+NAME: hey-search-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(ha-mail-search-leader
"r" '("reply" . notmuch-search-reply-to-thread)
"R" '("reply-all" . notmuch-search-reply-to-thread-sender)
@ -700,19 +694,19 @@ The bindings in =notmuch-search-mode= are available when looking at a list of me
(define-key notmuch-search-mode-map (kbd "P") 'hey-notmuch-move-sender-to-papertrail)
(define-key notmuch-search-mode-map (kbd "f") 'hey-notmuch-move-sender-to-thefeed)
(define-key notmuch-search-mode-map (kbd "C") 'hey-notmuch-reply-later)
#+END_SRC
#+end_src
** Org Integration
The gods ordained that Mail and Org should dance together, so step one is composing mail with org:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org-mime
:config
(ha-local-leader
:keymaps 'notmuch-message-mode-map
"s" '("send" . notmuch-mua-send-and-exit)
"m" '("mime it" . org-mime-htmlize)))
#+END_SRC
#+end_src
A new option is to use [[https://github.com/jeremy-compostella/org-msg][org-msg]], so let's try it:
#+BEGIN_SRC emacs-lisp :noweb yes
#+begin_src emacs-lisp :noweb yes
(use-package org-msg
:init
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
@ -733,30 +727,29 @@ A new option is to use [[https://github.com/jeremy-compostella/org-msg][org-msg]
,*Howard*
/One Emacs to rule them all/
,#+end_signature"))
#+END_SRC
#+end_src
The idea of linking org documents to email could be nice, however, the =ol-notmuch= package in the [[https://elpa.nongnu.org/nongnu/org-contrib.html][org-contrib]] package needs a maintainer.
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(use-package ol-notmuch
:after org
:straight (:type built-in)
:config (add-to-list 'org-modules 'ol-notmuch))
#+END_SRC
#+end_src
To use, read a message and save a link to it with ~SPC o l~. Next, in an org document, create a link with ~SPC m l~. Now, you can return to the message from that document with ~SPC m o~. Regardless, I may need to store a local copy when I upgrade Org.
* Display Configuration
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package doom-modeline
:config
(setq doom-modeline-mu4e t))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-email)
;;; ha-email.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate configuration file for email using Notmuch.

View file

@ -1,15 +1,14 @@
#+TITLE: My RSS Feeds
#+AUTHOR: Howard Abrams
#+DATE: 2018-08-08 August
#+FILETAGS: :elfeed:
A literate programming file for configuring =elfeed= in Emacs.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-config --- ElFeed configuration. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,19 +22,19 @@ A literate programming file for configuring =elfeed= in Emacs.
;; Using `find-file-at-point', and tangle the file to recreate this one .
;;
;;; Code:
#+END_SRC
#+end_src
* Configuring Elfeed
Let's get our feeds from a collection of org mode files. By default, Doom configures =rmh-elfeed-org-files= to [[file:~/Dropbox/org/elfeed.org][elfeed.org]] in =org-directory=, so that will be fine.
By setting this variable, we configure elfeed to use elfeed:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq rmh-elfeed-org-files (list (f-join hamacs-source-dir "ha-feed-reader.org")))
#+END_SRC
#+end_src
While I would like to share the /status/ of my reads, so ...
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package elfeed
:init
(setq elfeed-db-directory "~/dropbox/.elfeed/")
@ -63,11 +62,11 @@ While I would like to share the /status/ of my reads, so ...
(interactive)
(elfeed-search-untag-all 'unread)
(elfeed-search-update))
#+END_SRC
#+end_src
According to Ben Maughan and [[http://pragmaticemacs.com/emacs/to-eww-or-not-to-eww/][this Pragmatic Emacs essay]], we could easily browse an article in the GUI browser instead of EWW with capital B:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package elfeed
:config
(defun elfeed-show-visit-gui ()
@ -78,10 +77,10 @@ According to Ben Maughan and [[http://pragmaticemacs.com/emacs/to-eww-or-not-to-
:bind (:map elfeed-show-mode-map
("B" . elfeed-show-visit-gui)))
#+END_SRC
#+end_src
Quick way to start and jump to my world of feeds.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-elfeed-persp-start ()
"Create an ELFEED workspace and start my ELFEED client."
(interactive)
@ -92,15 +91,15 @@ Quick way to start and jump to my world of feeds.
"Switch to the ELFEED workspace and load the next buffer."
(interactive)
(persp-switch "feeds"))
#+END_SRC
#+end_src
And some global keys to display them in the =apps= menu:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader
"a f" '("elfeed switch" . ha-elfeed-persp-switch)
"a F" '("elfeed start" . ha-elfeed-persp-start))
#+END_SRC
#+end_src
* The Feeds :elfeed:
** Personal :personal:
*** [[http://www.howardism.org/index.xml][Howardisms]] :mustread:
@ -250,10 +249,10 @@ Has some good, thought-provoking essays.
* Technical Artifacts :noexport:
Let's /provide/ a name so we can =require= the file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-feed-reader)
;;; ha-feed-reader.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming file for configuring elfeed.

View file

@ -1,15 +1,14 @@
#+TITLE: IRC and Bitlbee Interface
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-12-10
#+FILETAGS: :emacs:
A literate programming configuration file for IRC communiction.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-irc.el --- configuration for IRC communication. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,11 +22,11 @@ A literate programming configuration file for IRC communiction.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
My IRC /needs/ are basic, but I'm not sure which I want to use.
** ERC
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package erc-hl-nicks
:after erc)
@ -71,20 +70,20 @@ My IRC /needs/ are basic, but I'm not sure which I want to use.
(erc :server "howardabrams.com" :port 7777)
(sit-for 2)
(erc-cmd-QUOTE (format "PASS %s:%s" username password))))
#+END_SRC
#+end_src
I like to make sure the text formats to the size of the window:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-erc-resize-text ()
"Resize the ERC text to fill the current window."
(interactive)
(setq erc-fill-column (1- (window-width))))
#+END_SRC
#+end_src
* ZNC Server
I'm using my own ZNC server, and I need to log in with a password stored in my GPG auth storage:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-erc-reconnect-password ()
"Send the reconnection password string."
(interactive)
@ -94,11 +93,11 @@ I'm using my own ZNC server, and I need to log in with a password stored in my G
(username (plist-get auth-first :user))
(password (funcall (plist-get auth-first :secret))))
(erc-cmd-QUOTE (format "PASS %s:%s" username password))))
#+END_SRC
#+end_src
* Key Bindings
Quick way to start and jump to my IRC world.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-irc-persp-start ()
"Create an IRC workspace and start my IRC client."
(interactive)
@ -110,46 +109,46 @@ Quick way to start and jump to my IRC world.
(interactive)
(persp-switch "irc")
(call-interactively 'erc-track-switch-buffer))
#+END_SRC
#+end_src
And some global keys to display them:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader
"a i" '("irc switch" . ha-irc-persp-switch)
"a I" '("irc start" . ha-irc-persp-start))
#+END_SRC
#+end_src
Let's create a leader for this mode:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(general-create-definer ha-irc-leader
:states '(normal visual motion)
:keymaps '(erc-mode-map)
:prefix "SPC m"
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+END_SRC
#+end_src
And a quick shortcut to call it:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-irc-leader
"o" '("next channel" . erc-track-switch-buffer)
"w" '("resize text" . ha-erc-resize-text)
"r" '("reconnect" . ha-erc-connect-irc)
"p" '("send password" . ha-erc-reconnect-password))
#+END_SRC
#+end_src
* Display Configuration
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq doom-modeline-irc t
doom-modeline-irc-stylize 'identity)
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
This will =provide= a code name, so that we can =require= this.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-irc)
;;; ha-irc.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming configuration file for IRC.

View file

@ -1,15 +1,14 @@
#+TITLE: Pasting the Org Clipboard
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-15
#+FILETAGS: :emacs:
A literate programming file of functions for formatting the clipboard.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; org-clipboard --- Functions for formatting the clipboard. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,7 +22,7 @@ A literate programming file of functions for formatting the clipboard.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
I would like to paste the formatted contents of the clipboard into an Org file /as org-formatted text/.
* The Clipboard
@ -32,18 +31,18 @@ Functions to help convert content from the operating system's clipboard into org
Each operating system as a different way of working with the clipboard, so let's create an operating-system abstraction:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-get-clipboard ()
"Returns a list where the first entry is the content type,
either :html or :text, and the second is the clipboard contents."
(if (ha-running-on-macos?)
(ha-get-mac-clipboard)
(ha-get-linux-clipboard)))
#+END_SRC
#+end_src
Let's define the clipboard for a Mac. The challenge here is that we need to binary unpack the data from a call to Applescript.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-get-mac-clipboard ()
"Returns a list where the first entry is the content type,
either :html or :text, and the second is the clipboard contents."
@ -65,11 +64,11 @@ Let's define the clipboard for a Mac. The challenge here is that we need to bina
(mapcar #'hex-pack-bytes))))
(decode-coding-string
(mapconcat #'byte-to-string byte-seq "") 'utf-8))))
#+END_SRC
#+end_src
And define the same interface for Linux. Keep in mind, we need the exit code from calling a process, so I am going to define/use a helper function (that really should go into the piper project).
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-get-linux-clipboard ()
"Return the clipbaard for a Unix-based system. See `ha-get-clipboard'."
(cl-destructuring-bind (exit-code contents)
@ -83,13 +82,13 @@ And define the same interface for Linux. Keep in mind, we need the exit code fro
(with-temp-buffer
(list (apply 'call-process program nil (current-buffer) nil args)
(buffer-string))))
#+END_SRC
#+end_src
* Converting from Slack
We can assume that most non-HTML text could be Slack-like:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-slack-to-markdown-buffer ()
"Odd function that converts Slacks version of Markdown (where
code is delimited with triple backticks) into a more formal
@ -116,23 +115,23 @@ four-space indent markdown style."
(next-line)
(beginning-of-line)
(insert " ")))))))
#+END_SRC
#+end_src
* Converting to Org
Let's work top-down at this point with the interactive function that inserts the clipboard into the current buffer:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-yank-clipboard ()
"Yanks (pastes) the contents of the Apple Mac clipboard in an
org-mode-compatible format."
(interactive)
(insert (ha-org-clipboard)))
#+END_SRC
#+end_src
The heavy lifting, however is done by this function. Note that I will need another function to tidy up the output from =pandoc= that will be more to my liking.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-clipboard ()
"Return the contents of the clipboard in org-mode format."
(seq-let (type contents) (ha-get-clipboard)
@ -161,22 +160,22 @@ The heavy lifting, however is done by this function. Note that I will need anoth
(goto-char (point-min))
(while (re-search-forward search nil t)
(replace-match replace)))))
#+END_SRC
#+end_src
* Keybinding to Paste into Org Files
We just need to bind it to the /local/ mode key sequence:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(with-eval-after-load 'ha-org
(ha-org-leader "y" 'ha-org-yank-clipboard))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(provide 'ha-org-clipboard)
;;; ha-org-clipboard.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming version of functions for formatting the clipboard.

View file

@ -5,7 +5,7 @@
A literate programming configuration file for extending the Journaling capabilities.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; org-journaling --- Configuring journals in org. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
@ -23,11 +23,11 @@ A literate programming configuration file for extending the Journaling capabilit
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
Using the [[https://github.com/bastibe/org-journal][org-journal]] project to easily create /daily/ journal entries:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org-journal
:init
(setq org-journal-dir "~/journal"
@ -36,45 +36,45 @@ Using the [[https://github.com/bastibe/org-journal][org-journal]] project to eas
org-journal-file-type 'daily
org-journal-file-format "%Y%m%d")
:config
#+END_SRC
#+end_src
Notice that the rest of this file's contents is /contained/ in this =config= section!
And let's put a /leader key/ sequence for it (Doom-specific):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader "f j" '("journal" . org-journal-new-entry))
#+END_SRC
#+end_src
In normal Org file, I like large headers, but in my Journal, where each task is a header, I want them smaller:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(add-hook 'org-journal-mode-hook
(lambda ()
(set-face-attribute 'org-level-1 nil :height 1.2)
(set-face-attribute 'org-level-2 nil :height 1.1)
(set-face-attribute 'org-level-3 nil :height 1.0)))
#+END_SRC
#+end_src
But new files could use /my formatting/ (which is different than the options available in the project):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-auto-insert-file (rx "journal/" (zero-or-more any) (= 8 digit)) "journal")
#+END_SRC
#+end_src
This depends on the following [[file:~/.doom.d/snippets/org-journal-mode/__journal][snippet/template file]]:
#+BEGIN_SRC snippet :tangle ~/other/hamacs/templates/journal
#+begin_src snippet :tangle ~/other/hamacs/templates/journal
#+TITLE: Journal Entry- `(ha-journal-file-datestamp)`
$0
#+END_SRC
#+end_src
Note that when I create a new journal entry, I want a title that should insert a date to match the file's name, not necessarily the current date (see below).
* Journal Filename to Date
Since the Journal's filename represents a date, I should be able to get the "date" associated with a file.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-journal-file-date (&optional datename)
"Returns a Lisp date-timestamp based on the format of the current filename,
or DATENAME if given."
@ -89,24 +89,24 @@ or DATENAME if given."
(month (string-to-number (match-string 2 datename)))
(year(string-to-number (match-string 1 datename))))
(encode-time 0 0 0 day month year)))
#+END_SRC
#+end_src
Using the "date" associated with a file, we can create our standard timestamp:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-journal-file-datestamp (&optional datename)
"Return a string of the buffer's date (based on the file's name)."
(format-time-string "%e %b %Y (%A)" (ha-journal-file-date datename)))
#+END_SRC
#+end_src
Close the =use-package= call:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
)
#+END_SRC
#+end_src
* Journal Capture
Capturing a task (that when uncompleted, would then spillover to following days) could go to the daily journal entry. This requires a special function that opens today's journal, but specifies a non-nil prefix argument in order to inhibit inserting the heading, as =org-capture= will insert the heading.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-journal-find-location ()
(org-journal-new-entry t)
(org-narrow-to-subtree)
@ -118,11 +118,11 @@ Capturing a task (that when uncompleted, would then spillover to following days)
(function org-journal-find-location)
"* %?\n\n %i\n\n From: %a"
:empty-lines 1 :jump-to-captured t :immediate-finish t))
#+END_SRC
#+end_src
* Next and Previous File
Sometimes it is obvious what is the /next file/ based on the one I'm currently reading. For instance, in my journal entries, the filename is a number that can be incremented. Same with presentation files...
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun split-string-with-number (string)
"Returns a list of three components of the string, the first is
the text prior to any numbers, the second is the embedded number,
@ -133,22 +133,22 @@ Sometimes it is obvious what is the /next file/ based on the one I'm currently r
(list (substring string 0 start)
(substring string start end)
(if end (substring string end) "")))))
#+END_SRC
#+end_src
Which means that the following defines this function:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(ert-deftest split-string-with-number-test ()
(should (equal (split-string-with-number "abc42xyz") '("abc" "42" "xyz")))
(should (equal (split-string-with-number "42xyz") '("" "42" "xyz")))
(should (equal (split-string-with-number "abc42") '("abc" "42" "")))
(should (equal (split-string-with-number "20140424") '("" "20140424" "")))
(should (null (split-string-with-number "abcxyz"))))
#+END_SRC
#+end_src
Given this splitter function, we create a function that takes some sort of operator and return a new filename based on the conversion that happens:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun find-file-number-change (f)
(let* ((filename (buffer-file-name))
(parts (split-string-with-number
@ -159,11 +159,11 @@ Given this splitter function, we create a function that takes some sort of opera
(nth 0 parts)
new-name
(nth 2 parts))))
#+END_SRC
#+end_src
And this allows us to create two simple functions that can load the "next" and "previous" files:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun find-file-increment ()
"Takes the current buffer, and loads the file that is 'one
more' than the file contained in the current buffer. This
@ -171,9 +171,9 @@ And this allows us to create two simple functions that can load the "next" and "
incremented."
(interactive)
(find-file (find-file-number-change '1+)))
#+END_SRC
#+end_src
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun find-file-decrement ()
"Takes the current buffer, and loads the file that is 'one
less' than the file contained in the current buffer. This
@ -181,14 +181,14 @@ And this allows us to create two simple functions that can load the "next" and "
decremented."
(interactive)
(find-file (find-file-number-change '1-)))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-org-journaling)
;;; ha-org-journaling.el ends here
#+END_SRC
#+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~

View file

@ -1,15 +1,14 @@
#+TITLE: Publishing my Website with Org
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-12-22
#+FILETAGS: :emacs:
A literate programming file for publishing my website using org.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; org-publishing --- Publishing my website using org. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,24 +22,24 @@ A literate programming file for publishing my website using org.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
While the Emacs community have a plethora of options for generating a static website from org-formatted files, I keep my pretty simple, and just use the standard =org-publish= feature.
While the Emacs community have a plethora of options for generating a static website from org-formatted files, I keep my pretty simple, and use the standard =org-publish= feature.
While the following packages come with Emacs, they aren't necessarily loaded:
#+BEGIN_SRC emacs-lisp :results silent
#+begin_src emacs-lisp :results silent
(require 'ox-rss)
(require 'ox-publish)
#+END_SRC
#+end_src
Variable settings:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq org-export-with-broken-links t
org-mode-websrc-directory "/Volumes/Personal/dropbox/website"
org-mode-publishing-directory (concat (getenv "HOME") "/website-public/"))
#+END_SRC
#+end_src
* The Projects
I separate my /website/ into distinct projects separately built:
@ -49,7 +48,7 @@ I separate my /website/ into distinct projects separately built:
- =blog-rss= :: Regenerate the feeder files
- =org-notes= :: Optionally render a non-web site collection of notes.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq org-publish-project-alist
`(("all"
:components ("blog-content" "blog-static" "org-notes" "blog-rss"))
@ -134,11 +133,11 @@ I separate my /website/ into distinct projects separately built:
:publishing-directory ,(concat org-mode-publishing-directory "/other/")
:recursive t
:publishing-function org-publish-attachment)))
#+END_SRC
#+end_src
* Including Sections
In the project definitions, I reference a =pre-= and =postamble= that allow me to inject some standard HTML file headers and footers:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-mode-blog-preamble (options)
"The function that creates the preamble top section for the blog.
OPTIONS contains the property list from the org-mode export."
@ -151,10 +150,10 @@ In the project definitions, I reference a =pre-= and =postamble= that allow me t
OPTIONS contains the property list from the org-mode export."
(let ((base-directory (plist-get options :base-directory)))
(org-babel-with-temp-filebuffer (expand-file-name "bottom.html" base-directory) (buffer-string))))
#+END_SRC
#+end_src
Another helper function for the content of website is to make sure to update =index.org=, so that the RSS gets generated.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-mode-blog-prepare (&optional options)
"`index.org' should always be exported so touch the file before publishing."
(let* ((base-directory (plist-get options :base-directory))
@ -163,31 +162,31 @@ Another helper function for the content of website is to make sure to update =in
(set-buffer-modified-p t)
(save-buffer 0))
(kill-buffer buffer)))
#+END_SRC
#+end_src
* Keybindings
Make it easy to publish all or just some of my website:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(with-eval-after-load 'ha-org
(ha-org-leader
"p" '(:ignore t :which-key "publishing")
"p a" '("all" . org-publish-all)
"p p" '("project" . org-publish-project)))
#+END_SRC
#+end_src
And let's put a /leader key/ sequence for my favorite file on my website:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader
"f h" '(:ignore t :which-key "howards")
"f h i" '("website index" . (lambda ()
(find-file (expand-file-name "index.org" "~/website")))))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= it:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-org-publishing)
;;; ha-org-publishing.el ends here
#+END_SRC
#+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~

View file

@ -1,15 +1,14 @@
#+TITLE: My Sprint Calculations and Support
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-25
#+FILETAGS: :emacs:
A literate program for configuring org files for work-related notes.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,34 +22,32 @@ A literate program for configuring org files for work-related notes.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand tiny ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
I want a single keybinding that always displays the current Sprint note file, regardless of what Sprint it is. This means, I need to have functions that can calculate what this is.
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
In order to have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:snippets/org-mode/__sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:snippets/org-mode/__sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
This template needs the following functions:
- =sprint-current-name= to be both the numeric label as well as the nickname
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
* Naming Sprints
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both...mostly for myself.
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both for myself.
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is just a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
#+BEGIN_SRC emacs-lisp :noweb yes
(defvar sprint-nicknames
(--map (replace-regexp-in-string " *[:#].*" "" (first it))
'<<sprint-names-2022()>>)
"List of 26 Sprint Nicknames from A to Z.")
#+END_SRC
#+begin_src emacs-lisp :noweb yes
(defvar sprint-nicknames
(--map (replace-regexp-in-string " *[:#].*" "" (first it))
'<<sprint-names-2022()>>)
"List of 26 Sprint Nicknames from A to Z.")
#+end_src
** 2022
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
@ -177,111 +174,103 @@ Came up with a list of somewhat well-known cities throughout the world (at least
- zippy-zinder
* Sprint Boundaries
Function to help in calculating dates and other features of a two-week sprint that starts on Thursday and ends on a Wednesday...hey, that is just how we do things at my job.
Function to help in calculating dates and other features of a two-week sprint that starts on Thursday and ends on a Wednesday… how we work at my job.
Emacs have an internal rep of a time.
#+BEGIN_SRC emacs-lisp
(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 (and date (stringp date))
(->> date ; Shame that encode-time
parse-time-string ; can't take a string, as
(-take 6) ; this seems excessive...
(--map (if (null it) 0 it))
(apply 'encode-time))
date))
#+END_SRC
#+begin_src emacs-lisp
(defun get-date-time (date)
"My functions can't deal with dates as string, so this will
parse DATE as a string, or return the value given otherwise."
(if (and date (stringp date))
(->> date ; Shame that encode-time
parse-time-string ; can't take a string, as
(-take 6) ; this seems excessive...
(--map (if (null it) 0 it))
(apply 'encode-time))
date))
#+end_src
** Sprint Numbering
My Sprint starts on Thursday, but this sometimes changed, so let's make this a variable:
#+begin_src emacs-lisp
(defvar sprint-starting-day 2 "The day of the week the sprint begins, where 0 is Sunday.")
#+end_src
#+BEGIN_SRC emacs-lisp
(defvar sprint-starting-day 2 "The day of the week the sprint begins, where 0 is Sunday.")
#+END_SRC
We label our sprint based on the week number that it starts. Note that on a Monday, I want to consider that we are still numbering from last week.
#+begin_src emacs-lisp
(defun sprint-week-num (&optional date)
"Return the week of the current year (or DATE), but starting
the week at Thursday to Wednesday."
(let* ((d (get-date-time date))
(dow (nth 6 (decode-time d))) ; Day of the week 0=Sunday
(week (->> d ; Week number in the year
(format-time-string "%U")
string-to-number)))
(if (>= dow sprint-starting-day)
(1+ week)
week)))
#+end_src
We label our sprint based on the week number that it starts. However, on a Monday, I want to consider that we are still numbering from last week.
Let's have these tests to make sure, and yeah, perhaps we update this at the beginning of each year.
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-week-num-test ()
(should (= (sprint-week-num "2021-03-15") 11)) ;; Monday previous week
(should (= (sprint-week-num "2021-03-16") 12)) ;; Tuesday current week
(should (= (sprint-week-num "2021-03-19") 12)))
#+end_src
#+BEGIN_SRC emacs-lisp
(defun sprint-week-num (&optional date)
"Return the week of the current year (or DATE), but starting
the week at Thursday to Wednesday."
(let* ((d (get-date-time date))
(dow (nth 6 (decode-time d))) ; Day of the week 0=Sunday
(week (->> d ; Week number in the year
(format-time-string "%U")
string-to-number)))
(if (>= dow sprint-starting-day)
(1+ week)
week)))
#+END_SRC
Let's have a few tests to make sure, and yeah, perhaps we update this at the beginning of each year.
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest sprint-week-num-test ()
(should (= (sprint-week-num "2021-03-15") 11)) ;; Monday previous week
(should (= (sprint-week-num "2021-03-16") 12)) ;; Tuesday current week
(should (= (sprint-week-num "2021-03-19") 12)))
#+END_SRC
Since my sprints are currently two weeks long, we could be see that on even week numbers, the /sprint/ is actually the previous week's number.
My company has sprints two weeks long, we could be see that on even week numbers, the /sprint/ is actually the previous week's number.
And it appears that my PM for this year, is a week number behind.
#+BEGIN_SRC emacs-lisp
(defun sprint-number (&optional date)
"Return the current sprint number, with some assumptions that
each sprint is two weeks long, starting on Thursday."
(interactive)
(let ((num (sprint-week-num date)))
(if (cl-oddp num)
(- num 2)
(- num 1))))
#+END_SRC
#+begin_src emacs-lisp
(defun sprint-number (&optional date)
"Return the current sprint number, with some assumptions that
each sprint is two weeks long, starting on Thursday."
(interactive)
(let ((num (sprint-week-num date)))
(if (cl-oddp num)
(- num 2)
(- num 1))))
#+end_src
And some tests to verify that:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest sprint-number-test ()
(should (= (sprint-number "2021-03-15") 9))
(should (= (sprint-number "2021-03-16") 11))
(should (= (sprint-number "2021-03-22") 11))
(should (= (sprint-number "2021-03-23") 11))
(should (= (sprint-number "2021-03-29") 11))
(should (= (sprint-number "2021-03-30") 13)))
#+END_SRC
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-number-test ()
(should (= (sprint-number "2021-03-15") 9))
(should (= (sprint-number "2021-03-16") 11))
(should (= (sprint-number "2021-03-22") 11))
(should (= (sprint-number "2021-03-23") 11))
(should (= (sprint-number "2021-03-29") 11))
(should (= (sprint-number "2021-03-30") 13)))
#+end_src
** Sprint File Name
I create my org-file notes based on the Sprint number.
#+begin_src emacs-lisp
(defun sprint-current-file (&optional date)
"Return the absolute pathname to the current sprint file."
(let ((d (get-date-time date)))
(expand-file-name
(format "~/Notes/Sprint-%s-%02d.org"
(format-time-string "%Y" d)
(sprint-number d)))))
#+end_src
#+BEGIN_SRC emacs-lisp
(defun sprint-current-file (&optional date)
"Return the absolute pathname to the current sprint file."
(let ((d (get-date-time date)))
(expand-file-name
(format "~/Notes/Sprint-%s-%02d.org"
(format-time-string "%Y" d)
(sprint-number d)))))
#+END_SRC
So what that means, is given a particular date, I should expect to be able to find the correct Sprint file name:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest sprint-current-file-test ()
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-07")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-09")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-10")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-13")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-14")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-17"))))
#+END_SRC
So given a particular date, I should expect to be able to find the correct Sprint file name:
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-current-file-test ()
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-07")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-09")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-10")))
(should (s-ends-with? "Sprint-2019-05.org" (sprint-current-file "2019-02-13")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-14")))
(should (s-ends-with? "Sprint-2019-07.org" (sprint-current-file "2019-02-17"))))
#+end_src
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun sprint-current-find-file (&optional date)
"Load the `org-mode' note associated with my current sprint."
(interactive)
@ -290,140 +279,138 @@ Daily note-taking goes into my sprint file notes, so this interactive function m
org-annotate-file-storage-file filename)
(add-to-list 'org-agenda-files filename)
(find-file filename)))
#+END_SRC
#+end_src
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
#+BEGIN_SRC emacs-lisp
(defun sprint-current-name (&optional date)
"Return the default name of the current sprint (based on DATE)."
(let* ((d (get-date-time date))
(sprint-order (/ (1- (sprint-number d)) 2))
(nickname (nth sprint-order sprint-nicknames)))
(format "Sprint %s-%02d %s"
(format-time-string "%Y" d)
(sprint-number d)
nickname)))
#+END_SRC
#+begin_src emacs-lisp
(defun sprint-current-name (&optional date)
"Return the default name of the current sprint (based on DATE)."
(let* ((d (get-date-time date))
(sprint-order (/ (1- (sprint-number d)) 2))
(nickname (nth sprint-order sprint-nicknames)))
(format "Sprint %s-%02d %s"
(format-time-string "%Y" d)
(sprint-number d)
nickname)))
#+end_src
These test won't pass any more, as the nickname of the sprint changes from year to year.
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest sprint-current-name-test ()
(should (equal "Sprint 2019-05 (candid-cannes)" (sprint-current-name "2019-02-13")))
(should (equal "Sprint 2019-07 (darling-dadu)" (sprint-current-name "2019-02-14"))))
#+END_SRC
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-current-name-test ()
(should (equal "Sprint 2019-05 (candid-cannes)" (sprint-current-name "2019-02-13")))
(should (equal "Sprint 2019-07 (darling-dadu)" (sprint-current-name "2019-02-14"))))
#+end_src
** Sprint Start and End
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first thursday/ of the year, so I need to begin the year changing this value. I should fix this.
#+BEGIN_SRC emacs-lisp
(defun sprint-range (&optional number-or-date)
"Return a list of three entries, start of the current sprint,
end of the current sprint, and the start of the next sprint.
Each date value should be formatted with `format-time-string'."
(let* ((num (if (or (null number-or-date) (stringp number-or-date))
(sprint-number number-or-date)
number-or-date))
(year-start "2020-01-02") ; First Thursday of the year
(time-start (-> year-start ; Converted to time
get-date-time
float-time))
(day-length (* 3600 24)) ; Length of day in seconds
(week-length (* day-length 7))
(sprint-start (time-add time-start (* week-length (1- num))))
(sprint-next (time-add time-start (* week-length (1+ num))))
(sprint-end (time-add sprint-next (- day-length))))
(list sprint-start sprint-end sprint-next)))
#+END_SRC
#+begin_src emacs-lisp
(defun sprint-range (&optional number-or-date)
"Return a list of three entries, start of the current sprint,
end of the current sprint, and the start of the next sprint.
Each date value should be formatted with `format-time-string'."
(let* ((num (if (or (null number-or-date) (stringp number-or-date))
(sprint-number number-or-date)
number-or-date))
(year-start "2020-01-02") ; First Thursday of the year
(time-start (-> year-start ; Converted to time
get-date-time
float-time))
(day-length (* 3600 24)) ; Length of day in seconds
(week-length (* day-length 7))
(sprint-start (time-add time-start (* week-length (1- num))))
(sprint-next (time-add time-start (* week-length (1+ num))))
(sprint-end (time-add sprint-next (- day-length))))
(list sprint-start sprint-end sprint-next)))
#+end_src
Format the start and end so that we can insert this directly in the org file:
#+BEGIN_SRC emacs-lisp
(defun sprint-date-range (&optional number-or-date)
"Return an `org-mode' formatted date range for a given sprint
number or date, `NUMBER-OR-DATE' or if `nil', the date range of
the current sprint."
(seq-let (sprint-start sprint-end) (sprint-range number-or-date)
(let* ((formatter "%Y-%m-%d %a")
(start (format-time-string formatter sprint-start))
(end (format-time-string formatter sprint-end)))
(format "[%s]--[%s]" start end))))
#+END_SRC
#+begin_src emacs-lisp
(defun sprint-date-range (&optional number-or-date)
"Return an `org-mode' formatted date range for a given sprint
number or date, `NUMBER-OR-DATE' or if `nil', the date range of
the current sprint."
(seq-let (sprint-start sprint-end) (sprint-range number-or-date)
(let* ((formatter "%Y-%m-%d %a")
(start (format-time-string formatter sprint-start))
(end (format-time-string formatter sprint-end)))
(format "[%s]--[%s]" start end))))
#+end_src
And let's have a test to validate this:
#+BEGIN_SRC emacs-lisp
(ert-deftest sprint-date-range ()
(should (equal (sprint-date-range 7)
(sprint-date-range "2020-02-17"))))
#+END_SRC
And validate with a test:
#+begin_src emacs-lisp
(ert-deftest sprint-date-range ()
(should (equal (sprint-date-range 7)
(sprint-date-range "2020-02-17"))))
#+end_src
** Pre-scheduled Dates
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
#+BEGIN_SRC emacs-lisp
(defun sprint-date-from-start (days &optional formatter)
"Given a number of DAYS from the start of the sprint, return a formatted date string."
(let* ((day-length (* 3600 24))
(start (car (sprint-range)))
(adate (time-add start (* day-length days))))
(if formatter
(format-time-string formatter adate)
(format-time-string "%Y-%m-%d %a" adate))))
#+END_SRC
#+begin_src emacs-lisp
(defun sprint-date-from-start (days &optional formatter)
"Given a number of DAYS from the start of the sprint, return a formatted date string."
(let* ((day-length (* 3600 24))
(start (car (sprint-range)))
(adate (time-add start (* day-length days))))
(if formatter
(format-time-string formatter adate)
(format-time-string "%Y-%m-%d %a" adate))))
#+end_src
* Other Date Functions
The following functions /were/ helpful at times. But I'm not sure I will use them.
#+BEGIN_SRC emacs-lisp :tangle no
(defun sprint-num-days (time-interval)
"Converts a TIME-INTERVAL to a number of days."
(let ((day-length (* 3600 24)))
(round (/ (float-time time-interval) day-length))))
#+END_SRC
#+begin_src emacs-lisp :tangle no
(defun sprint-num-days (time-interval)
"Converts a TIME-INTERVAL to a number of days."
(let ((day-length (* 3600 24)))
(round (/ (float-time time-interval) day-length))))
#+end_src
#+BEGIN_SRC emacs-lisp :tangle no
(defun sprint-day-range (&optional date)
"Returns a list of two values, the number of days from the
start of the sprint, and the number of days to the end of the
sprint based on DATE if given, or from today if DATE is `nil'."
(seq-let (sprint-start sprint-end) (sprint-range date)
(let* ((now (get-date-time date))
(starting (time-subtract sprint-start now))
(ending (time-subtract sprint-end now)))
(list (sprint-num-days starting) (sprint-num-days ending)))))
#+END_SRC
#+begin_src emacs-lisp :tangle no
(defun sprint-day-range (&optional date)
"Returns a list of two values, the number of days from the
start of the sprint, and the number of days to the end of the
sprint based on DATE if given, or from today if DATE is `nil'."
(seq-let (sprint-start sprint-end) (sprint-range date)
(let* ((now (get-date-time date))
(starting (time-subtract sprint-start now))
(ending (time-subtract sprint-end now)))
(list (sprint-num-days starting) (sprint-num-days ending)))))
#+end_src
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest sprint-day-range ()
;; This sprint starts on 2/13 and ends on 2/26
(should (equal '(0 13) (sprint-day-range "2020-02-13")))
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
#+END_SRC
#+begin_src emacs-lisp :tangle no
(ert-deftest sprint-day-range ()
;; This sprint starts on 2/13 and ends on 2/26
(should (equal '(0 13) (sprint-day-range "2020-02-13")))
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
#+end_src
#+BEGIN_SRC emacs-lisp :tangle no
(defun sprint-day-start (&optional date)
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Thursday, this would return -1."
(first (sprint-day-range date)))
#+begin_src emacs-lisp :tangle no
(defun sprint-day-start (&optional date)
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Thursday, this would return -1."
(first (sprint-day-range date)))
(defun sprint-day-end (&optional date)
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Wednesday, this would return 3."
(second (sprint-day-range date)))
#+END_SRC
(defun sprint-day-end (&optional date)
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Wednesday, this would return 3."
(second (sprint-day-range date)))
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-org-sprint)
;;; ha-org-sprint.el ends here
#+END_SRC
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-org-sprint)
;;; ha-org-sprint.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~

View file

@ -5,11 +5,11 @@
A literate programming file for making Org file more readable.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-org-word-processor --- Making Org file more readable. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,13 +23,13 @@ A literate programming file for making Org file more readable.
;; Using `find-file-at-point', and tangle the file to recreate this one .
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
I like having org-mode files look more like a word processor than having it look like programming code. But that is just me.
I like having org-mode files look more like a word processor than having it look like programming code. But that is me.
* General Org Settings
Since I use ellipsis in my writing… to /change/ how org renders a collapsed heading.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq org-pretty-entities t
org-ellipsis "⤵" ; …, ➡, ⚡, ▼, ↴, , ∞, ⬎, ⤷, ⤵
org-agenda-breadcrumbs-separator " ❱ "
@ -37,24 +37,24 @@ Since I use ellipsis in my writing… to /change/ how org renders a collapsed he
org-special-ctrl-a/e t ; Note: Need to get this working with Evil!
org-src-fontify-natively t ; Pretty code blocks
org-hide-emphasis-markers t)
#+END_SRC
Oh, and as I indent lists, they should change the /bulleting/ in a particular sequence. If I begin with an =*= asterisk, I walk down the chain, but with the dashed bullets (my default choice), I just stay with dashed bullets. Numeric bullets should cycle:
#+end_src
Oh, and as I indent lists, they should change the /bulleting/ in a particular sequence. If I begin with an =*= asterisk, I walk down the chain, but with the dashed bullets (my default choice), I stay with dashed bullets. Numeric bullets should cycle:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq org-list-demote-modify-bullet '(("*" . "+") ("+" . "-") ("-" . "-")
("1." . "a.") ("a." . "1.")))
#+END_SRC
#+end_src
The =org-indent-indentation-per-level=, which defaults to =2= doesnt really work well with variable-width fonts, so lets make the spaces at the beginning of the line fixed:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org
:custom-face (org-indent ((t (:inherit fixed-pitch)))))
#+END_SRC
#+end_src
** Typographic Quotes
According to [[http://endlessparentheses.com/prettify-your-quotation-marks.html][Artur Malabarba]] of [[http://endlessparentheses.com/prettify-you-apostrophes.html][Endless Parenthesis]] blog, I type either a /straight single or double quote/, ", Emacs actually inserts Unicode rounded quotes, like “this”. This idea isnt how a file is /displayed/, but actually how the file is /made/. Time will tell if this idea works with my auxiliary functions on my phone, like [[https://play.google.com/store/apps/details?id=com.orgzly&hl=en_US&gl=US][Orgzly]] and [[https://github.com/amake/orgro][Orgro]].
Stealing his function, and updating it a bit, so that “quotes” just work to insert /rounded/ quotation marks:
#+BEGIN_SRC emacs-lisp
Stealing his function, and updating it a bit, so that “quotes” work to insert /rounded/ quotation marks:
#+begin_src emacs-lisp
(defun ha--insert-round-quotes (opening closing)
"Insert rounded quotes in prose but not inside code.
The OPENING and CLOSING variables are either or .
@ -93,11 +93,11 @@ Stealing his function, and updating it a bit, so that “quotes” just work to
((looking-back starting-anew) (insert-pair))
((looking-at existing-endq) (goto-char (match-end 0)))
(t (insert closing)))))))
#+END_SRC
#+end_src
Now we can take advantage of the abstraction for “double quotes”:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-round-quotes ()
"Insert “” and leave point in the middle.
Inside a code-block, just call `self-insert-command'.
@ -106,11 +106,11 @@ Now we can take advantage of the abstraction for “double quotes”:
(ha--insert-round-quotes "“" "”"))
(define-key org-mode-map "\"" #'ha-round-quotes)
#+END_SRC
#+end_src
And something similar for single quotes:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-apostrophe ()
"Insert and leave point in the middle.
Inside a code-block, just call `self-insert-command'.
@ -119,13 +119,13 @@ And something similar for single quotes:
(ha--insert-round-quotes "" ""))
(define-key org-mode-map "'" #'ha-apostrophe)
#+END_SRC
#+end_src
*Note:* I still need to worry about how quotes affect [[file:ha-org.org::*Spell Checking][spell checking]].
What would be nice, is that if I end quotes using the functions above, that if I immediately delete, I delete both pairs.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-delete-quote-pairs (&optional N)
"If positioned between two quote symbols, delete the last.
Used as advice to `org-delete-backward-char' function."
@ -135,10 +135,10 @@ What would be nice, is that if I end quotes using the functions above, that if I
(advice-add #'org-delete-backward-char :before #'ha-delete-quote-pairs)
#+END_SRC
#+end_src
Can we do the same with ellipses?
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-insert-dot-or-ellipsis ()
"Insert a `.' unless two have already be inserted.
In this case, insert an ellipsis instead."
@ -156,10 +156,10 @@ Can we do the same with ellipses?
(define-key org-mode-map "." #'ha-insert-dot-or-ellipsis)
#+END_SRC
#+end_src
Now Im getting obsessive with elongating dashes:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-insert-long-dash ()
"Insert a `.' unless two have already be inserted.
In this case, insert an ellipsis instead."
@ -179,32 +179,32 @@ Now Im getting obsessive with elongating dashes:
(t (insert "-")))))
(define-key org-mode-map "-" #'ha-insert-long-dash)
#+END_SRC
#+end_src
* Org Beautify
I really want to use the Org Beautify package, but it overrides my darker themes (and all I really want is headlines to behave).
First step is to make all Org header levels to use the variable font, and be the same color as the default text:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when window-system
(let ((default-color (face-attribute 'default :foreground)))
(dolist (face '(org-level-1 org-level-2 org-level-3 org-level-4 org-level-5 org-level-6 org-level-7 org-level-8))
(set-face-attribute face nil :height 1.1
:foreground default-color :weight 'bold :font ha-variable-font))))
#+END_SRC
#+end_src
Next, we just need to change the header sizes:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when window-system
(set-face-attribute 'org-level-1 nil :height 2.2)
(set-face-attribute 'org-level-2 nil :height 1.8)
(set-face-attribute 'org-level-3 nil :height 1.4)
(set-face-attribute 'org-level-4 nil :height 1.2))
#+END_SRC
#+end_src
While we are at it, lets make sure the code blocks are using my fixed with font:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(when window-system
(dolist (face '(org-block org-code org-verbatim org-table org-drawer
org-table org-formula org-special-keyword org-block
@ -214,28 +214,28 @@ While we are at it, lets make sure the code blocks are using my fixed with fo
(set-face-attribute 'org-table nil :height 1.0)
(set-face-attribute 'org-formula nil :height 1.0)
#+END_SRC
#+end_src
Not sure why the above code removes the color of =org-verbatim=, so lets make it stand out slightly:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(set-face-attribute 'org-verbatim nil :foreground "#aaaacc")
#+END_SRC
#+end_src
And some slight adjustments to the way blocks are displayed:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(set-face-attribute 'org-block-begin-line nil :background "#282828")
(set-face-attribute 'org-block nil :height 0.95)
(set-face-attribute 'org-block-end-line nil :background "#262626")
#+END_SRC
#+end_src
And decrease the prominence of the property drawers:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(set-face-attribute 'org-drawer nil :height 0.8)
(set-face-attribute 'org-property-value nil :height 0.85)
(set-face-attribute 'org-special-keyword nil :height 0.85)
#+END_SRC
#+end_src
This process allows us to use =variable-pitch= mode for all org documents.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org
:hook (org-mode . variable-pitch-mode))
#+END_SRC
#+end_src
* Org Modern
The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a lot of what I was doing in this file.
#+begin_src emacs-lisp
@ -247,8 +247,8 @@ The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a
#+end_src
I like the smaller code blocks as well as the <2022-06-16 Thu> timestamps.
* Checkboxes
#+BEGIN_SRC emacs-lisp
According to an idea by [[https://jft.home.blog/2019/07/17/use-unicode-symbol-to-display-org-mode-checkboxes/][Huy Trần]], (and expanded by the [[https://github.com/minad/org-modern][org-modern]] project), we can prettify the list checkboxes. To make completed tasks more distinguishable, he changed the colors:
#+begin_src emacs-lisp
(defface org-checkbox-done-text
'((t (:foreground "#71696A" :strike-through t)))
"Face for the text part of a checked org-mode checkbox.")
@ -297,20 +297,12 @@ However, I'm just going to have to write a function to clean this.
bol (zero-or-more space) "#+END" (zero-or-more any) eol "\n")
(optional bol (zero-or-more space) eol "\n")) nil t)
(replace-match (match-string 1) nil :no-error)))
#+END_SRC
#+end_src
Now that is some complicated regular expressions.
* Pasting
I like the idea that I will paste HTML text from the clipboard and have it converted to org-formatted text:
#+BEGIN_SRC emacs-lisp
(defun ha-org-paste ()
(interactive)
(if (eq system-type 'gnu/linux)
(shell-command "xclip -t text/html -o | pandoc -r html -w org" t)))
#+END_SRC
* Presentations
Used to use [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] for showing org files as presentations. Converted to use [[https://github.com/rlister/org-present][org-present]]. I love the /hooks/ as that makes it easier to pull out much of my =demo-it= configuration. My concern with =org-present= is that it only jumps from one top-level to another top-level header.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org-present
:init
(defvar ha-org-present-mode-line mode-line-format "Cache previous mode-line format state")
@ -328,8 +320,8 @@ Used to use [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] for sho
(defun hide-these (patterns)
(when patterns
(hide-this (car patterns))
(hide-these (cdr patterns))))
(hide-this (car patterns))
(hide-these (cdr patterns))))
(save-excursion
(hide-these (list (rx bol (zero-or-more space)
@ -368,13 +360,42 @@ Used to use [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] for sho
:hook
(org-present-mode . org-present-start)
(org-present-mode-quit . org-present-end))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
#+END_SRC
Note, according to [[https://www.reddit.com/r/emacs/comments/vahsao/orgmode_use_capitalized_property_keywords_or/][this discussion]] (and especially [[https://scripter.co/org-keywords-lower-case/][this essay]]), Im switching over to lower-case version of org properties. Using this helper function:
#+begin_src emacs-lisp
(defun modi/lower-case-org-keywords ()
"Lower case Org keywords and block identifiers.
Example: \"#+TITLE\" -> \"#+title\"
\"#+BEGIN_EXAMPLE\" -> \"#+begin_example\"
Inspiration:
https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0."
(interactive)
(save-excursion
(goto-char (point-min))
(let ((case-fold-search nil)
(count 0)
;; All keywords can be found with this expression:
;; (org-keyword-re "\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)")
;; Match examples: "#+foo bar", "#+foo:", "=#+foo=", "~#+foo~",
;; "#+foo", "“#+foo”", ",#+foo bar",
;; "#+FOO_bar<eol>", "#+FOO<eol>".
;;
;; Perhap I want the #+begin_src and whatnot:
(org-keyword-re (rx line-start (optional (zero-or-more space))
"#+" (group (or "BEGIN" "END") "_" (one-or-more alpha)))))
(while (re-search-forward org-keyword-re nil :noerror)
(setq count (1+ count))
(replace-match (downcase (match-string-no-properties 1)) :fixedcase nil nil 1))
(message "Lower-cased %d matches" count))))
#+end_src
Let's provide a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-org-word-processor)
;;; ha-org-word-processor.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 making Org file more readable.

View file

@ -1,15 +1,14 @@
#+TITLE: General Org-Mode Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-18
#+FILETAGS: :emacs:
A literate programming file for configuring org-mode and those files.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha --- Org configuration. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -24,10 +23,10 @@ A literate programming file for configuring org-mode and those files.
;;
;;; Code:
#+END_SRC
#+end_src
* Use Package
Org is a /large/ complex beast with a gazillion settings, so I discuss these later in this document.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package org
:mode ("\\.org" . org-mode) ; Addresses an odd warning
:init
@ -47,22 +46,22 @@ Org is a /large/ complex beast with a gazillion settings, so I discuss these lat
<<org-return-key>>
<<global-keybindings>>
<<org-keybindings>>)
#+END_SRC
#+end_src
One other helper routine is a =general= macro for org-mode files:
#+NAME: ha-org-leader
#+BEGIN_SRC emacs-lisp :tangle no
#+name: ha-org-leader
#+begin_src emacs-lisp :tangle no
(general-create-definer ha-org-leader
:states '(normal visual motion)
:keymaps 'org-mode-map
:prefix "SPC m"
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+END_SRC
#+end_src
* Initialization Section
Begin by initializing these org variables:
#+NAME: variables
#+BEGIN_SRC emacs-lisp :tangle no
#+name: variables
#+begin_src emacs-lisp :tangle no
(setq org-return-follows-link t
org-adapt-indentation nil ; Don't physically change files
org-startup-indented t ; Visually show paragraphs indented
@ -90,28 +89,28 @@ Begin by initializing these org variables:
org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t)
#+END_SRC
#+end_src
* Configuration Section
I pretend that my org files are word processing files that wrap automatically:
#+NAME: visual-hook
#+BEGIN_SRC emacs-lisp :tangle no
#+name: visual-hook
#+begin_src emacs-lisp :tangle no
(add-hook 'org-mode-hook #'visual-line-mode)
#+END_SRC
#+end_src
Files that end in =.txt= are still org files to me:
#+NAME: text-files
#+BEGIN_SRC emacs-lisp :tangle no
#+name: text-files
#+begin_src emacs-lisp :tangle no
(add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
(add-to-list 'safe-local-variable-values '(org-content . 2))
#+END_SRC
#+end_src
*Note:* Org mode files with the =org-content= variable setting will collapse two levels headers. Let's allow that without the need to approve that.
** Better Return
Hitting the ~Return~ key in an org file should format the following line based on context. For instance, at the end of a list, insert a new item.
We begin with the interactive function that calls our code if we are at the end of the line.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-return ()
"If at the end of a line, do something special based on the
information about the line by calling `ha-org-special-return',
@ -120,13 +119,13 @@ We begin with the interactive function that calls our code if we are at the end
(if (eolp)
(ha-org-special-return)
(org-return)))
#+END_SRC
#+end_src
And bind it to the Return key:
#+NAME: org-return-key
#+BEGIN_SRC emacs-lisp :tangle no
#+name: org-return-key
#+begin_src emacs-lisp :tangle no
(define-key org-mode-map (kbd "RET") #'ha-org-return)
#+END_SRC
#+end_src
What should we do if we are at the end of a line?
- Given a prefix, call =org-return= as usual in an org file.
@ -137,7 +136,7 @@ What should we do if we are at the end of a line?
I should break this function into smaller bits ...
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-special-return (&optional ignore)
"Add new list item, heading or table row with RET.
A double return on an empty element deletes it.
@ -177,11 +176,11 @@ I should break this function into smaller bits ...
(t
(org-return)))))
#+END_SRC
#+end_src
How do we know if we are in a list item? Lists end with two blank lines, so we need to make sure we are also not at the beginning of a line to avoid a loop where a new entry gets created with one blank line.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-really-in-item-p ()
"Return item beginning position when in a plain list, nil otherwise.
Unlike `org-in-item-p', this works around an issue where the
@ -192,30 +191,30 @@ How do we know if we are in a list item? Lists end with two blank lines, so we n
(when location
(goto-char location))
(org-in-item-p))))
#+END_SRC
#+end_src
The org API allows getting the context associated with the /current element/. This could be a line-level symbol, like paragraph or =list-item=, but always when the point isn't /inside/ a bold or italics item. You know how HTML distinguishes between /block/ and /inline/ elements, org doesn't. So, let's make a function that makes that distinction:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-line-element-context ()
"Return the symbol of the current block element, e.g. paragraph or list-item."
(let ((context (org-element-context)))
(while (member (car context) '(verbatim code bold italic underline))
(setq context (org-element-property :parent context)))
context))
#+END_SRC
#+end_src
** Tasks
I need to add a /blocked/ state:
#+NAME: org-todo
#+BEGIN_SRC emacs-lisp :tangle no
#+name: org-todo
#+begin_src emacs-lisp :tangle no
(setq org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)")
(sequence "BLOCKED(b)" "|" "CANCELLED(c)")))
#+END_SRC
#+end_src
And I would like to have cute little icons for those states:
#+NAME: org-font-lock
#+BEGIN_SRC emacs-lisp
#+name: org-font-lock
#+begin_src emacs-lisp
(dolist (m '(org-mode org-journal-mode))
(font-lock-add-keywords m ; A bit silly but my headers are now
`(("^\\*+ \\(TODO\\) " ; shorter, and that is nice canceled
@ -233,11 +232,11 @@ And I would like to have cute little icons for those states:
;; file or the behavior).
("^ +\\([-*]\\) "
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))))
#+END_SRC
#+end_src
** Meetings
I've notice that while showing a screen while taking meeting notes, I don't always like showing other windows, so I created this function to remove distractions during a meeting.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun meeting-notes ()
"Call this after creating an org-mode heading for where the notes for the meeting
should be. After calling this function, call 'meeting-done' to reset the environment."
@ -249,11 +248,11 @@ I've notice that while showing a screen while taking meeting notes, I don't alwa
(text-scale-set 2) ; readable by others
(fringe-mode 0)
(message "When finished taking your notes, run meeting-done."))
#+END_SRC
#+end_src
Of course, I need an 'undo' feature when the meeting is over…
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun meeting-done ()
"Attempt to 'undo' the effects of taking meeting notes."
(interactive)
@ -261,10 +260,10 @@ Of course, I need an 'undo' feature when the meeting is over…
(text-scale-set 0) ; Reset the font size increase
(fringe-mode 1)
(winner-undo)) ; Put the windows back in place
#+END_SRC
#+end_src
** Searching
Now that my paragraphs in an org file are on a single line, I need this less, but being able to use an /indexed search system/, like [[https://ss64.com/osx/mdfind.html][mdfind]] on Macos, or [[https://www.lesbonscomptes.com/recoll/][recoll]] on Linux, gives better results that line-oriented search systems, like =grep=. Lets create operating-system functions the command line for searching:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-search-notes--macos (phrase path)
"Return the indexed search system command on MACOS, mdfind.
Including the parameters using the PHRASE on the PATH(s)."
@ -279,10 +278,10 @@ Now that my paragraphs in an org file are on a single line, I need this less, bu
(format "recoll -t -a -b %s" phrase))
#+END_SRC
#+end_src
This function calls these operating-system functions, but returns the matching files as a /single string/ (where each file is wrapped in single quotes, and all joined together, separated by spaces. This function also allows me to /not-match/ backup files and whatnot.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-search-notes--files (phrase path)
"Return an escaped string of all files matching PHRASE.
On a Mac, this search is limited by PATH"
@ -296,11 +295,11 @@ This function calls these operating-system functions, but returns the matching f
(--map (format "'%s'" it))
(s-join " "))))
#+END_SRC
#+end_src
The =ha-search-notes= function prompts for the phrase to search, and then searches through the =org-directory= path to acquire the matching files. It then feeds that list to =grep= (and the [[help:grep][grep function]] in order to display a list of matches that I can jump to.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-search-notes (phrase &optional path)
"Search files in PATH for PHRASE and display in a grep mode buffer."
(interactive "sSearch notes for: ")
@ -311,33 +310,33 @@ The =ha-search-notes= function prompts for the phrase to search, and then search
(grep (format "%s -ni -m 1 '%s' %s" command regexp files))))
#+END_SRC
#+end_src
Eventually, I would like to change the output so that the title of the Org mode is displayed instead of the first match, but that is good enough.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader "f n" '("find notes" . ha-search-notes))
#+END_SRC
#+end_src
** Misc
*** Babel Blocks
I use [[https://orgmode.org/worg/org-contrib/babel/intro.html][org-babel]] (obviously) and dont need confirmation before evaluating a block:
#+NAME: ob-configuration
#+BEGIN_SRC emacs-lisp :tangle no
#+name: ob-configuration
#+begin_src emacs-lisp :tangle no
(setq org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t)
#+END_SRC
#+end_src
Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off.
#+NAME: no-flycheck-in-org
#+BEGIN_SRC emacs-lisp :tangle no
#+name: no-flycheck-in-org
#+begin_src emacs-lisp :tangle no
(defun disable-flycheck-in-org-src-block ()
(setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
(add-hook 'org-src-mode-hook 'disable-flycheck-in-org-src-block)
#+END_SRC
#+end_src
And turn on ALL the languages:
#+NAME: ob-languages
#+BEGIN_SRC emacs-lisp :tangle no
#+name: ob-languages
#+begin_src emacs-lisp :tangle no
(org-babel-do-load-languages 'org-babel-load-languages
'((shell . t)
(js . t)
@ -348,7 +347,7 @@ And turn on ALL the languages:
(dot . t)
(css . t)
(plantuml . t)))
#+END_SRC
#+end_src
*** REST Web Services
:PROPERTIES:
:header-args: :var user-agent="my-super-agent"
@ -398,13 +397,13 @@ And lets try this:
*** Graphviz
The [[https://graphviz.org/][graphviz project]] can be written in org blocks, and then rendered as an image:
#+NAME: ob-graphviz
#+BEGIN_SRC emacs-lisp :tangle no
#+name: ob-graphviz
#+begin_src emacs-lisp :tangle no
(add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot"))
#+END_SRC
#+end_src
For example:
#+BEGIN_SRC dot :file support/ha-org-graphviz-example.png :exports file :results replace file
#+begin_src dot :file support/ha-org-graphviz-example.png :exports file :results replace file
digraph G {
graph [bgcolor=transparent];
edge [color=white];
@ -419,25 +418,25 @@ For example:
A -> H;
E -> G;
}
#+END_SRC
#+end_src
#+ATTR_ORG: :width 400px
#+RESULTS:
#+attr_org: :width 400px
#+results:
[[file:support/ha-org-graphviz-example.png]]
*** PlantUML
Need to install and configure Emacs to work with [[https://plantuml.com/][PlantUML]]. Granted, this is easier now that [[http://orgmode.org/worg/org-contrib/babel][Org-Babel]] natively supports [[http://eschulte.github.io/babel-dev/DONE-integrate-plantuml-support.html][blocks of plantuml code]]. First, [[https://plantuml.com/download][download the Jar]].
#+BEGIN_SRC sh
#+begin_src sh
curl -o ~/bin/plantuml.jar https://github.com/plantuml/plantuml/releases/download/v1.2022.4/plantuml-1.2022.4.jar
#+END_SRC
#+end_src
After installing the [[https://github.com/skuro/plantuml-mode][plantuml-mode]], we need to reference the location:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package plantuml-mode
:straight (:host github :repo "skuro/plantuml-mode")
:init
(setq org-plantuml-jar-path (expand-file-name "~/bin/plantuml.jar")))
#+END_SRC
#+end_src
With some [[file:snippets/org-mode/plantuml][YASnippets]], I have =<p= to start a general diagram, and afterwards (while still in the org-mode file), type one of the following to expand as an example:
- =activity= :: https://plantuml.com/activity-diagram-betastart
@ -451,12 +450,12 @@ With some [[file:snippets/org-mode/plantuml][YASnippets]], I have =<p= to start
You may be wondering how such trivial terms can be used as expansions in an org file. Well, the trick is that each snippets has a =condition= that calls the following predicate function, that make the snippets context aware:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-nested-in-plantuml-block ()
"Predicate is true if point is inside a Plantuml Source code block in org-mode."
(equal "plantuml"
(plist-get (cadr (org-element-at-point)) :language)))
#+END_SRC
#+end_src
Here is a sequence diagram example to show how is looks/works:
#+begin_src plantuml :file ha-org-plantuml-example.png :exports file :results file
@ -471,12 +470,12 @@ Here is a sequence diagram example to show how is looks/works:
@enduml
#+end_src
#+ATTR_ORG: :width 800px
#+attr_org: :width 800px
[[file:ha-org-plantuml-example.png]]
*** Next Image
When I create images or other artifacts that I consider /part/ of the org document, I want to have them based on the org file, but with a prepended number. Keeping track of what numbers are now free is difficult, so for a /default/ let's figure it out:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-next-image-number (&optional prefix)
(when (null prefix)
(if (null (buffer-file-name))
@ -490,20 +489,20 @@ When I create images or other artifacts that I consider /part/ of the org docume
(while (re-search-forward png-reg nil t)
(setq largest (max largest (string-to-number (match-string-no-properties 1)))))
(format "%s-%02d" prefix (1+ largest)))))
#+END_SRC
#+end_src
** Keybindings
Global keybindings available to all file buffers:
#+NAME: global-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
#+name: global-keybindings
#+begin_src emacs-lisp :tangle no
(ha-leader
"o l" '("store link" . org-store-link)
"o x" '("org capture" . org-capture)
"o c" '("clock out" . org-clock-out))
#+END_SRC
#+end_src
Bindings specific to org files:
#+NAME: org-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
#+name: org-keybindings
#+begin_src emacs-lisp :tangle no
(evil-define-key '(normal motion operator visual) org-mode-map "gu" #'org-up-element)
(ha-org-leader
@ -557,27 +556,27 @@ Bindings specific to org files:
"n b" '("block" . org-narrow-to-block)
"n e" '("element" . org-narrow-to-element)
"n w" '("widen" . widen))
#+END_SRC
#+end_src
* Supporting Packages
** Exporters
Limit the number of exporters to the ones that I would use:
#+NAME: ox-exporters
#+BEGIN_SRC emacs-lisp
#+name: ox-exporters
#+begin_src emacs-lisp
(setq org-export-backends '(ascii html icalendar md odt))
#+END_SRC
#+end_src
I have a special version of tweaked [[file:elisp/ox-confluence.el][Confluence exporter]] for my org files:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package ox-confluence
:after org
:straight nil ; Located in my "elisp" directory
:config
(ha-org-leader
"E" '("to confluence" . ox-export-to-confluence)))
#+END_SRC
#+end_src
And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode][graphviz-dot-mode]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package graphviz-dot-mode
:mode "\\.dot\\'"
:init
@ -586,18 +585,18 @@ And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode]
graphviz-dot-auto-indent-on-newline t
graphviz-dot-auto-indent-on-braces t
graphviz-dot-auto-indent-on-semi t))
#+END_SRC
#+end_src
And we can install company support:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(use-package company-graphviz-dot)
#+END_SRC
#+end_src
** Focused Work
:LOGBOOK:
CLOCK: [2022-02-11 Fri 11:05]--[2022-02-11 Fri 11:21] => 0:16
:END:
I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-work.html][approach to focused work]],
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package async)
(use-package ha-focus
@ -606,23 +605,23 @@ I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-w
(ha-leader
"o f" '("begin focus" . ha-focus-begin)
"o F" '("break focus" . ha-focus-break)))
#+END_SRC
#+end_src
** Spell Checking
Let's hook some spell-checking into org files, and actually all text files. Im making this particularly delicious.
First, we turn on =abbrev-mode=. While this package comes with Emacs, check out [[https://masteringemacs.org/article/correcting-typos-misspellings-abbrev][Mickey Petersen's overview]] of using this package for auto-correcting typos.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(setq-default abbrev-mode t)
#+END_SRC
#+end_src
In general, /fill/ the list, by moving the point to the /end/ of some word, and type ~C-x a g~ (or, in /normal state/, type ~SPC x d~):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader "x d" '("add abbrev" . kadd-global-abbrev))
#+END_SRC
#+end_src
The idea is that you can correct a typo /and remember/ it. Perhaps calling [[help:edit-abbrevs][edit-abbrevs]] to making any fixes to that list.
Next, I create a special /auto-correcting function/ that takes advantage of Evils [[help:evil-prev-flyspell-error][evil-prev-flyspell-error]] to jump back to the last spelling mistake (as I often notice the mistake after entering a few words), and call the interactive [[help:ispell-word][ispell-word]]. What makes this delicious is that I then call [[help:define-global-abbrev][define-global-abbrev]] to store both the mistake and the correction so that automatically typing that mistake again, is corrected.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-fix-last-spelling (count)
"Jump to the last misspelled word, and correct it.
This adds the correction to the global abbrev table so that any
@ -635,12 +634,12 @@ Next, I create a special /auto-correcting function/ that takes advantage of Evil
(bad-word (match-string 0)))
(ispell-word)
(define-global-abbrev bad-word (buffer-substring-no-properties start-word (point)))))))
#+END_SRC
#+end_src
Since this auto-correction needs to happen in /insert/ mode, I have bound a few keys, including ~CMD-s~ and ~M-s~ (twice) to fixing this spelling mistake, and jumping back to where I am. If the spelling mistake is /obvious/, I use ~C-;~ to call [[help:flyspell-auto-correct-word][flyspell-auto-correct-word]]. However, I currently do not know how to use this cool feature with my =ha-fix-last-spelling= function (because I dont know when that function is done).
For this to work, we use [[https://www.emacswiki.org/emacs/FlySpell][flyspell]] mode to highlight the misspelled words, and the venerable [[https://www.emacswiki.org/emacs/InteractiveSpell][ispell]] for correcting.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package flyspell
:hook (text-mode . flyspell-mode)
:bind (("M-S" . ha-fix-last-spelling) ; This is j-k-s on the Moonlander. Hrm.
@ -663,12 +662,12 @@ For this to work, we use [[https://www.emacswiki.org/emacs/FlySpell][flyspell]]
"s c" '("correct word" . flyspell-auto-correct-word)
"s p" '("previous misspell" . evil-prev-flyspell-error)
"s n" '("next misspell" . evil-next-flyspell-error)))
#+END_SRC
#+end_src
Sure, the keys, ~[ s~ and ~] s~ can jump to misspelled words, and use ~M-$~ to correct them, but I'm getting used to these leaders.
According to [[http://endlessparentheses.com/ispell-and-apostrophes.html][Artur Malabarba]], we can turn on rounded apostrophe's, like == (left single quotation mark). The idea is to not send the quote to the sub-process:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun endless/replace-apostrophe (args)
"Don't send to the subprocess."
(cons (replace-regexp-in-string
@ -686,13 +685,13 @@ According to [[http://endlessparentheses.com/ispell-and-apostrophes.html][Artur
(cdr args))))
(advice-add #'ispell-parse-output :filter-args #'endless/replace-quote)
#+END_SRC
#+end_src
The end result? No misspellings. Isnt this nice?
Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoValeriy/emacs-powerthesaurus][powerthesaurus]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package powerthesaurus
:bind ("M-T" . powerthesaurus-lookup-dwim)
:config
@ -702,37 +701,38 @@ Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoV
"s a" '("antonyms" . powerthesaurus-lookup-antonyms-dwim)
"s r" '("related" . powerthesaurus-lookup-related-dwim)
"s S" '("sentence" . powerthesaurus-lookup-sentences-dwim)))
#+END_SRC
#+end_src
The key-bindings, keystrokes, and key-connections work well with ~M-T~ (notice the Shift), but to jump to specifics, we use a leader. Since the /definitions/ do not work, so let's use abo-abo's [[https://github.com/abo-abo/define-word][define-word]] project:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package define-word
:config
(ha-local-leader :keymaps 'text-mode-map
"s d" '("define this" . define-word-at-point)
"s D" '("define word" . define-word)))
#+END_SRC
#+end_src
** Grammar and Prose Linting
Flagging cliches, weak phrasing and other poor grammar choices.
*** Writegood
The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] is effective at highlighting passive and weasel words, but isnt integrated into =flycheck=:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(use-package writegood-mode
:hook ((org-mode . writegood-mode)))
#+END_SRC
#+end_src
And it reports obnoxious messages.
We install the =write-good= NPM:
#+BEGIN_SRC shell
#+begin_src shell
npm install -g write-good
#+END_SRC
#+end_src
And check that the following works:
#+BEGIN_SRC sh :results output
#+begin_src sh :results output
write-good --text="So it is what it is."
#+END_SRC
#+end_src
Now, lets connect it to flycheck:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package flycheck
:config
(flycheck-define-checker write-good
@ -743,16 +743,16 @@ Now, lets connect it to flycheck:
((warning line-start (file-name) ":" line ":" column ":" (message) line-end))
:modes (markdown-mode org-mode text-mode))
#+END_SRC
(add-to-list 'flycheck-checkers 'write-good))
#+end_src
*** Proselint
With overlapping goals to =write-good=, the [[https://github.com/amperser/proselint/][proselint]] project, once installed, can check for some English phrasings. I like =write-good= better, but I want this available for its level of /pedantic-ness/.
#+BEGIN_SRC sh
#+begin_src sh
brew install proselint
#+END_SRC
#+end_src
Next, create a configuration file, =~/.config/proselint/config= file, to turn on/off checks:
#+BEGIN_SRC js :tangle ~/.config/proselint/config.json :mkdirp yes
#+begin_src js :tangle ~/.config/proselint/config.json :mkdirp yes
{
"checks": {
"typography.diacritical_marks": false,
@ -760,12 +760,11 @@ Next, create a configuration file, =~/.config/proselint/config= file, to turn on
"consistency.spacing": false
}
}
#+END_SRC
#+end_src
And tell [[https://www.flycheck.org/][flycheck]] to use this:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package flycheck
#+END_SRC
:config
(add-to-list 'flycheck-checkers 'proselint)
;; And create the chain of checkers so that both work:
@ -812,15 +811,16 @@ Add =textlint= to the /chain/ for Org files:
:config
(setq flycheck-textlint-config (format "%s/.textlintrc" (getenv "HOME")))
(flycheck-add-next-checker 'proselint 'textlint))
#+end_src
** Distraction-Free Writing
[[https://christopherfin.com/writing/emacs-writing.html][Christopher Fin's essay]] inspired me to clean my writing room.
*** Write-room
For a complete focused, /distraction-free/ environment, for writing or concentrating, I'm using [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package writeroom-mode
:hook (writeroom-mode-disable . winner-undo)
:config
:init
(ha-leader "t W" '("writeroom" . writeroom-mode))
(ha-leader :keymaps 'writeroom-mode-map
"=" '("adjust width" . writeroom-adjust-width)
@ -830,23 +830,22 @@ For a complete focused, /distraction-free/ environment, for writing or concentra
("C-M-<" . writeroom-decrease-width)
("C-M->" . writeroom-increase-width)
("C-M-=" . writeroom-adjust-width)))
#+END_SRC
#+end_src
*** Olivetti
#+BEGIN_SRC emacs-lisp
The [[https://github.com/rnkn/olivetti][olivetti project]] sets wide margins and centers the text. It isnt better than Writeroom, but, it works well with Logos (below).
#+begin_src emacs-lisp
(use-package olivetti
:init
(setq-default olivetti-body-width 100)
:config
(ha-leader "t O" '("olivetti" . olivetti-mode))
:bind (:map olivetti-mode-map
("C-M-<" . olivetti-shrink)
("C-M->" . olivetti-expand)
("C-M-=" . olivetti-set-width)))
#+END_SRC
#+end_src
*** Logos
Trying out [[https://protesilaos.com/][Protesilaos Stavrou]]s [[https://protesilaos.com/emacs/logos][logos project]] as a replacement for [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package logos
:straight (:type git :protocol ssh :host gitlab :repo "protesilaos/logos")
:init
@ -870,13 +869,13 @@ Trying out [[https://protesilaos.com/][Protesilaos Stavrou]]s [[https://prote
(:states 'normal
"g [" 'logos-backward-page-dwim
"g ]" 'logos-forward-page-dwim))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's provide a name, to allow =require= to work:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-org)
;;; ha-org.el ends here
#+END_SRC
#+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~

View file

@ -1,15 +1,14 @@
#+TITLE: Personal Password Generator
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-01-11
#+FILETAGS: :emacs:
A literate programming version for Emacs code to generate and store passwords.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-passwords --- Emacs code to generate and store passwords. -*- lexical-binding: t; -*-
;;
;; © 2021-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,80 +22,77 @@ A literate programming version for Emacs code to generate and store passwords.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
Let's assume that I store a bunch of words in data files:
#+begin_src emacs-lisp
(defvar ha-passwords-data-files (list (expand-file-name "adjectives.txt"
(expand-file-name "data" hamacs-source-dir))
(expand-file-name "colors.txt"
(expand-file-name "data" hamacs-source-dir))
(expand-file-name "nouns.txt"
(expand-file-name "data" hamacs-source-dir)))
"List of file name containing a data lines for our password generator. Order of these files matter.")
#+BEGIN_SRC emacs-lisp
(defvar ha-passwords-data-files (list (expand-file-name "adjectives.txt"
(expand-file-name "data" hamacs-source-dir))
(expand-file-name "colors.txt"
(expand-file-name "data" hamacs-source-dir))
(expand-file-name "nouns.txt"
(expand-file-name "data" hamacs-source-dir)))
"List of file name containing a data lines for our password generator. Order of these files matter.")
(defvar ha-passwords-data nil
"Contains a list of lists of words that we can choose.")
#+END_SRC
(defvar ha-passwords-data nil
"Contains a list of lists of words that we can choose.")
#+end_src
You can see where I'm going with this, can't you? Let's read them into list variables.
#+begin_src emacs-lisp
(defun ha-passwords--read-data-file (filename)
(with-temp-buffer
(insert-file-contents filename)
(split-string (buffer-string) "\n" t)))
#+BEGIN_SRC emacs-lisp
(defun ha-passwords--read-data-file (filename)
(with-temp-buffer
(insert-file-contents filename)
(split-string (buffer-string) "\n" t)))
#+end_src
#+END_SRC
Now we get three or so words from our list of lists:
#+begin_src emacs-lisp
(defun ha-passwords-words ()
(unless ha-passwords-data
(setq ha-passwords-data
(--map (ha-passwords--read-data-file it) ha-passwords-data-files)))
Now we just get three or so words from our list of lists:
#+BEGIN_SRC emacs-lisp
(defun ha-passwords-words ()
(unless ha-passwords-data
(setq ha-passwords-data
(--map (ha-passwords--read-data-file it) ha-passwords-data-files)))
(--map (nth (random (length it)) it) ha-passwords-data))
#+END_SRC
(--map (nth (random (length it)) it) ha-passwords-data))
#+end_src
Let's make a password:
#+BEGIN_SRC emacs-lisp
(defun ha-passwords-generate (&optional separator)
(unless separator
(setq separator "-"))
#+begin_src emacs-lisp
(defun ha-passwords-generate (&optional separator)
(unless separator
(setq separator "-"))
(let* ((choices '("!" "@" "#" "$" "%" "^" "&" "*"))
(choice (random (length choices)))
(number (1+ choice)))
(->> (ha-passwords-words)
(s-join separator)
(s-capitalize)
(s-append (nth choice choices))
(s-append (number-to-string number)))))
#+END_SRC
(let* ((choices '("!" "@" "#" "$" "%" "^" "&" "*"))
(choice (random (length choices)))
(number (1+ choice)))
(->> (ha-passwords-words)
(s-join separator)
(s-capitalize)
(s-append (nth choice choices))
(s-append (number-to-string number)))))
#+end_src
#+BEGIN_SRC emacs-lisp
(defun generate-password (&optional separator)
(interactive)
(let ((passphrase (ha-passwords-generate separator)))
(kill-new passphrase)
(message "Random password: %s" passphrase)))
#+END_SRC
#+begin_src emacs-lisp
(defun generate-password (&optional separator)
(interactive)
(let ((passphrase (ha-passwords-generate separator)))
(kill-new passphrase)
(message "Random password: %s" passphrase)))
#+end_src
* Keybindings
Got make it easy to call:
#+BEGIN_SRC emacs-lisp
(ha-leader "a g" '("generate passwd" . generate-password))
#+END_SRC
#+begin_src emacs-lisp
(ha-leader "a g" '("generate passwd" . generate-password))
#+end_src
* Technical Artifacts :noexport:
This will =provide= a code name, so that we can =require= this.
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-passwords)
;;; ha-passwords.el ends here
#+END_SRC
#+begin_src emacs-lisp :exports none
(provide 'ha-passwords)
;;; ha-passwords.el ends here
#+end_src
#+DESCRIPTION: A literate programming version for Emacs code to generate and store passwords.

View file

@ -1,15 +1,14 @@
#+TITLE: Emacs Lisp Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2022-05-11
#+FILETAGS: :emacs:
A literate programming file for configuring Emacs for Lisp programming.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-lisp --- configuring Emacs for Lisp programming. -*- lexical-binding: t; -*-
;;
;; © 2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,45 +22,45 @@ A literate programming file for configuring Emacs for Lisp programming.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
While I program in a lot of languages, I seem to be writing all my helper tools and scripts in … Emacs Lisp. So Im cranking this up to 11.
While I program in a lot of languages, I seem to be writing all my helper tools and scripts in … Emacs Lisp. Im cranking this up to 11.
New, /non-literal/ source code comes from [[file:templates/emacs-lisp-mode.el][emacs-lisp-mode template]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-auto-insert-file (rx ".el" eol) "emacs-lisp-mode.el")
#+END_SRC
#+end_src
* Syntax Display
** Dim those Parenthesis
The [[https://github.com/tarsius/paren-face][paren-face]] project lowers the color level of parenthesis which I find better.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package paren-face
:hook (emacs-lisp-mode . paren-face-mode))
#+END_SRC
#+end_src
Show code examples with the [[https://github.com/xuchunyang/elisp-demos][elisp-demos]] package. This is really helpful.
#+BEGIN_SRC emacs-lisp
Show code examples with the [[https://github.com/xuchunyang/elisp-demos][elisp-demos]] package.
#+begin_src emacs-lisp
(use-package elisp-demos
:config
(advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1))
#+END_SRC
#+end_src
* Navigation and Editing
** Goto Definitions
Wilfreds [[https://github.com/Wilfred/elisp-def][elisp-def]] project does a better job at jumping to the definition of a symbol at the point, so:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package elisp-def
:hook (emacs-lisp-mode . elisp-def-mode))
#+END_SRC
#+end_src
This /should work/ with [[help:evil-goto-definition][evil-goto-defintion]], as that calls this list from [[help:evil-goto-definition-functions][evil-goto-definition-functions]]:
- [[help:evil-goto-definition-imenu][evil-goto-definition-imenu]]
- [[help:evil-goto-definition-semantic][evil-goto-definition-semantic]]
- [[help:evil-goto-definition-xref][evil-goto-definition-xref]] … and here is where this package will be called
- [[help:evil-goto-definition-xref][evil-goto-definition-xref]] … to show what calls a function
- [[help:evil-goto-definition-search][evil-goto-definition-search]]
I love packages that add functionality but I dont have to learn anything. However, Im running into an issue where I do a lot of my Emacs Lisp programming in org files, and would like to jump to the function definition where it is defined in the org file. Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, Ill call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse.
While I love packages that add functionality and I dont have to learn anything, Im running into an issue where I do a lot of my Emacs Lisp programming in org files, and would like to jump to the function definition /defined in the org file/. Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, Ill call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-org-code-block-jump (str pos)
"Go to a literate org file containing a symbol, STR.
The POS is ignored."
@ -86,25 +85,25 @@ I love packages that add functionality but I dont have to learn anything. How
(goto-line line))))
(add-to-list 'evil-goto-definition-functions 'ha-org-code-block-jump)
#+END_SRC
#+end_src
And in case I need to call it directly:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-goto-definition ()
(interactive)
(evil-inner-WORD))
#+END_SRC
#+end_src
** Clever Parenthesis
We need to make sure we keep the [[https://github.com/Fuco1/smartparens][smartparens]] project always in /strict mode/, because who wants to worry about paren-matching:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package smartparens
:custom
(smartparens-global-strict-mode t)
:hook
(prog-mode . smartparens-strict-mode))
#+END_SRC
#+end_src
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
#+begin_src emacs-lisp
(use-package evil-cleverparens
:after smartparens
:custom
@ -118,26 +117,26 @@ The [[https://github.com/luxbock/evil-cleverparens][evil-cleverparens]] solves h
:hook
(prog-mode . evil-cleverparens-mode)) ;; All the languages!
;; Otherwise: (emacs-lisp-mode . evil-cleverparens-mode)
#+END_SRC
#+end_src
The /trick/ to being effective with the [[https://www.emacswiki.org/emacs/ParEdit][paredit-family]] of extensions is learning the keys. The killer “app” is the slurp/barf sequence. Use the ~<~ key, in normal mode, to barf (or jettison)… in other words, /move/ the paren closer to the point. For instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(+ 41 (* ‖1 3)) ⟹ (+ 41 (* ‖1) 3)
#+END_SRC
#+end_src
Use the ~>~ key to /slurp/ in outside objects into the current expression… in other words, move the paren away from the point. For instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(+ 41 (* ‖1) 3) ⟹ (+ 41 (* ‖1 3))
#+END_SRC
#+end_src
*Opening Parens.* Those two keys seem straight-forward, but they behave differently when the are on the opening parens.
When the point (symbolized by ~‖~) is /on/ the opening paren, ~<~ moves the paren to the left. For instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(+ 41 ‖(* 1 3)) ⟹ (+ ‖(41 * 1 3))
#+END_SRC
#+end_src
And the ~>~ moves the paren to the right. For instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(+ 41 ‖(* 1 3)) ⟹ (+ 41 * ‖(1 3))
#+END_SRC
#+end_src
I would like to have a list of what keybindings that work in =normal= mode:
- ~M-h~ / ~M-l~ move back/forward by functions
@ -159,13 +158,13 @@ The other advantage is moving around by s-expressions. This takes a little getti
- ~(~ and ~)~ move up to the parent s-expression
We need a real-world example. Lets suppose we entered this:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(format "The sum of %d %d is %d" a b (+ a b))
#+END_SRC
#+end_src
But we forgot to define the =a= and =b= variables. One approach, after Escaping into the normal state, is to hit ~(~ to just to the beginning of the s-expression, and then type, ~M-(~ to wrap the expression, and type ~i~ to go into insert mode:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(‖ (format "The sum of %d %d is %d" a b (+ a b)))
#+END_SRC
#+end_src
And now we can enter the =let= expression.
Other nifty keybindings that I need to commit to muscle memory include:
@ -179,14 +178,14 @@ Other nifty keybindings that I need to commit to muscle memory include:
** Eval Current Expression
The [[https://github.com/xiongtx/eros][eros]] package stands for Evaluation Result OverlayS for Emacs Lisp, and basically shows what each s-expression is near the cursor position instead of in the mini-buffer at the bottom of the window.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package eros
:hook (emacs-lisp-mode . eros-mode))
#+END_SRC
#+end_src
A feature I enjoyed from Spacemacs is the ability to evaluate the s-expression currently containing the point. Not sure how they made it, but [[help:evil-cp-next-closing ][evil-cp-next-closing]] from cleverparens can help:
#+BEGIN_SRC emacs-lisp
#+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
@ -197,21 +196,20 @@ finds at that point."
(evil-cp-next-closing)
(evil-cp-forward-sexp)
(call-interactively 'eval-last-sexp)))
#+END_SRC
#+end_src
And we just need to bind it.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-prog-leader
"e c" '("current" . ha-eval-current-expression))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-programming-elisp)
;;; ha-programming-elisp.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: configuring Emacs for Lisp programming.

View file

@ -1,15 +1,14 @@
#+TITLE: Configuring Python in Emacs
#+AUTHOR: Howard X. Abrams
#+DATE: 2021-11-16
#+FILETAGS: :emacs:
A literate programming file for configuring Python.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-programming-python --- Python configuration. -*- lexical-binding: t; -*-
;;
;; © 2021-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,21 +22,21 @@ A literate programming file for configuring Python.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
The critical part of Python integration with Emacs is running LSP in Python using [[file:ha-programming.org::*direnv][direnv]]. And the only question to ask is if the Python we run it in Docker or in a virtual environment.
The critical part of Python integration with Emacs is running LSP in Python using [[file:ha-programming.org::*direnv][direnv]]. And the question to ask is if the Python we run it in Docker or in a virtual environment.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(general-create-definer ha-python-leader
:states '(normal visual motion)
:keymaps 'python-mode-map
:prefix "SPC m"
:global-prefix "<f17>"
:non-normal-prefix "S-SPC")
#+END_SRC
#+end_src
While Emacs supplies a Python editing environment, well still use =use-package= to grab the latest:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package python
:after projectile
:mode ("[./]flake8\\'" . conf-mode)
@ -53,7 +52,7 @@ While Emacs supplies a Python editing environment, well still use =use-packag
;; create these files for my Python projects:
(add-to-list 'projectile-project-root-files "requirements-dev.txt")
(add-to-list 'projectile-project-root-files "requirements-test.txt"))
#+END_SRC
#+end_src
** Virtual Environment
For a local virtual machine, simply put the following in your =.envrc= file:
#+begin_src conf
@ -62,9 +61,9 @@ layout_python3
That is pretty slick and simple.
The old way, that we still use if you need a particular version of Python, is to install [[https://github.com/pyenv/pyenv][pyenv]] globally:
#+BEGIN_SRC sh
#+begin_src sh
pip install pyenv
#+END_SRC
#+end_src
And have this in your =.envrc= file:
#+begin_src conf
@ -86,12 +85,12 @@ use_python() {
#+end_src
** Editing Python Code
Lets integrate this [[https://github.com/wbolster/evil-text-object-python][Python support for evil-text-object]] project:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package evil-text-object-python
:hook (python-mode . evil-text-object-python-add-bindings))
#+END_SRC
#+end_src
This allows me to delete a Python “block” using ~dal~.
** Docker Environment
Docker really allows you to isolate your project's environment. The downside is that you are using Docker and probably a bloated container. On my work laptop, a Mac, this creates a behemoth virtual machine that immediately spins the fans like a wind tunnel.
@ -109,7 +108,7 @@ CONTAINER_EXTRA_ARGS="--env SOME_ENV_VAR=${SOME_ENV_VAR}"
container_layout
#+end_src
** Unit Tests
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package python-pytest
:after python
:commands python-pytest-dispatch
@ -123,7 +122,7 @@ container_layout
"t T" '("function" . python-pytest-function)
"t r" '("repeat" . python-pytest-repeat)
"t p" '("dispatch" . python-pytest-dispatch)))
#+END_SRC
#+end_src
** Python Dependencies
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=):
@ -132,7 +131,7 @@ python-lsp-server[all]
#+end_src
*Note:* This does mean, you would have a =tox.ini= with this line:
#+BEGIN_SRC conf
#+begin_src conf
[tox]
minversion = 1.6
skipsdist = True
@ -146,7 +145,7 @@ python-lsp-server[all]
commands = stestr run {posargs}
stestr slowest
# ...
#+END_SRC
#+end_src
*** Pyright
Im using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
#+begin_src conf :tangle no
@ -155,16 +154,16 @@ pyright
The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package lsp-pyright
:hook (python-mode . (lambda () (require 'lsp-pyright)))
:init (when (executable-find "python3")
(setq lsp-pyright-python-executable-cmd "python3")))
#+END_SRC
#+end_src
* LSP Integration of Python
Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP Integration]] is complete, we can stitch the two projects together, by calling =lsp=. I oscillate between automatically turning on LSP mode with every Python file, but I sometimes run into issues when starting, so I turn it on with ~SPC m w s~.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package lsp-mode
;; :hook ((python-mode . lsp)))
:config
@ -258,11 +257,11 @@ Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integrat
"wq" '("shutdown server" . lsp-workspace-shutdown)
"wr" '("restart server" . lsp-workspace-restart)
"ws" '("start server" . lsp)))
#+END_SRC
#+end_src
* Project Configuration
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-python-configure-project (proj-directory)
"Configure PROJ-DIRECTORY for LSP and Python."
(interactive "DPython Project: ")
@ -302,14 +301,14 @@ I work with a lot of projects with my team where I need to /configure/ the proje
(unless (f-exists? ".dir-locals.el")
(with-temp-file ".dir-locals.el"
(insert "((nil . ((projectile-enable-caching . t))))")))))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-programming-python)
;;; ha-programming-python.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming file for configuring Python.

View file

@ -1,15 +1,14 @@
#+TITLE: Programming in Scheme for SICP
#+AUTHOR: Howard X. Abrams
#+DATE: 2022-03-01
#+FILETAGS: :emacs:
A literate programming file configuring Emacs.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-programming-scheme --- Configuration for Scheme. -*- lexical-binding: t; -*-
;;
;; © 2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,22 +22,21 @@ A literate programming file configuring Emacs.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Introduction
First, install MIT-Scheme, the Lisp dialect used throughout the SICP book:
=brew install mit-scheme= or =sudo apt install mit-scheme= .
#+BEGIN_SRC sh
#+begin_src sh
brew install mit-scheme
#+END_SRC
#+end_src
Or better yet, lets use Guile:
#+BEGIN_SRC sh
#+begin_src sh
brew install guile
#+END_SRC
#+end_src
* Geiser (Scheme Interface)
The [[https://www.nongnu.org/geiser/][geiser project]] attempts to be the interface between Emacs and various Schemes. Since I cant decide which to use, Ill install/configure them all.
#+BEGIN_SRC emacs-lisp
The [[https://www.nongnu.org/geiser/][geiser project]] attempts to be the interface between Emacs and all the Schemes in the world. Since I cant decide which to use, Ill install/configure them all.
#+begin_src emacs-lisp
(use-package geiser
:init
(setq geiser-mit-binary "/usr/local/bin/scheme"
@ -50,22 +48,22 @@ The [[https://www.nongnu.org/geiser/][geiser project]] attempts to be the interf
(use-package geiser-mit)
(use-package geiser-guile)
(use-package geiser-racket))
#+END_SRC
#+end_src
** Org Mode
:PROPERTIES:
:header-args:scheme: :session *scheming* :results value replace
:END:
Do we need a Scheme work for Org Babel? According to [[https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-scheme.html][this document]], we just need to make sure we add the =:session= variable to start the REPL.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package ob-scheme
:straight (:type built-in)
:config
(add-to-list 'org-babel-load-languages '(scheme . t)))
#+END_SRC
#+end_src
Since the version of Scheme hasn't been updated with the deprecation, and subsequent removal of =org-babel-get-header=, we include it here:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun org-babel-get-header (params key &optional others)
(delq nil
(mapcar
@ -73,80 +71,80 @@ Since the version of Scheme hasn't been updated with the deprecation, and subseq
params)))
#+END_SRC
#+end_src
Lets test it out by defining a variable:
#+BEGIN_SRC scheme
#+begin_src scheme
(define a 42)
#+END_SRC
#+end_src
And simply using it:
#+BEGIN_SRC scheme :var b=8
#+begin_src scheme :var b=8
(+ a b)
#+END_SRC
#+end_src
#+RESULTS:
: ;Value: 50
And what about Scheme-specific stuff needed for SICP?
#+BEGIN_SRC scheme
#+begin_src scheme
(inc 42)
#+END_SRC
#+end_src
** Install SICP
:PROPERTIES:
:header-args:scheme: :session sicp :results value replace
:END:
Lets get the book available as an Info page:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package sicp)
#+END_SRC
#+end_src
Still having difficulty getting the Scheme REPL to output the results back into this document. Lets try Racket...
Normally, I would just [[info:SICP][read the book]], however, if we want to read the [[file:~/.emacs.d/straight/build/sicp/sicp.info][sicp.info]] file, we need this, at least, temporarily:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.info\\'" . Info-mode))
#+END_SRC
#+end_src
* Racket
Actually, lets do this with [[https://racket-lang.org/][Racket]]:
#+BEGIN_SRC sh
#+begin_src sh
brew install racket
#+END_SRC
#+end_src
While Racket, as a Scheme, should work with Geiser (below), lets also get [[https://racket-mode.com/][racket-mode]] working:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package racket-mode
:config (setq racket-program "/usr/local/bin/racket"))
#+END_SRC
#+end_src
Can we get Racket working with Org?
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package ob-racket
:straight (:type git :protocol ssh :host github :repo "DEADB17/ob-racket")
:after org
:config
(add-to-list 'org-babel-load-languages '(racket . t)))
#+END_SRC
#+end_src
** Try It Out
:PROPERTIES:
:HEADER-ARGS:racket: :session racketeering :results value replace :lang racket
:END:
Working for values?
#+BEGIN_SRC racket
#+begin_src racket
(* 6 7)
#+END_SRC
#+end_src
#+RESULTS:
: 42
Working for output?
#+BEGIN_SRC racket :results output replace
#+begin_src racket :results output replace
(define str-1 "hello")
(define str-2 "world")
(define all (string-join (list str-1 str-2) ", "))
(display (string-titlecase all))
#+END_SRC
#+end_src
#+RESULTS:
: Hello, World
@ -158,16 +156,16 @@ The interface is horrendously slow, as the =:session= doesnt seem to work, an
:header-args:racket: :session *rsicp* :results value replace :lang sicp
:END:
If using [[https://docs.racket-lang.org/sicp-manual/SICP_Language.html][Racket for SICP]], install the SICP language:
#+BEGIN_SRC sh
#+begin_src sh
raco pkg install --auto --update-deps sicp
#+END_SRC
#+end_src
We now can give it a =#lang sicp= (or better yet, use the =:lang= header) to define certain SICP-specify features:
Lets try this now:
#+BEGIN_SRC racket
#+begin_src racket
(inc 42)
#+END_SRC
#+end_src
#+RESULTS:
: 43
@ -175,10 +173,10 @@ Lets try this now:
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-programming-scheme)
;;; ha-programming-scheme.el ends here
#+END_SRC
#+end_src
#+DESCRIPTION: A literate programming file configuring Emacs.

View file

@ -1,15 +1,14 @@
#+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
#+begin_src emacs-lisp :exports none
;;; general-programming --- Configuration for general languages. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,24 +22,22 @@ A literate programming file for helping me program.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+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
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
#+end_src
* General
The following work for all programming languages.
** direnv
Farm off commands into /virtual environments/:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package direnv
:init
(setq direnv--executable "/usr/local/bin/direnv"
@ -48,18 +45,18 @@ Farm off commands into /virtual environments/:
direnv-show-paths-in-summary t)
:config
(direnv-mode))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package flyspell
:hook (prog-mode . flyspell-prog-mode))
#+END_SRC
#+end_src
** Flycheck
Why use [[https://www.flycheck.org/][flycheck]] over the built-in =flymake=? Speed used to be the advantage, but Im now pushing much of this to LSP, so speed is less of an issue. What about when I am not using LSP? Also, since Ive hooked grammar checkers, I need this with global keybindings.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package flycheck
:init
(setq next-error-message-highlight t)
@ -96,17 +93,17 @@ Why use [[https://www.flycheck.org/][flycheck]] over the built-in =flymake=? Spe
"P v" '("verify-setup" . flycheck-verify-setup)
"P x" '("disable-checker" . flycheck-disable-checker)
"P t" '("toggle flycheck" . flycheck-mode)))
#+END_SRC
#+end_src
** Documentation
Ive used the [[http://kapeli.com/][Dash]] API Documentation browser (an external application) with Emacs, available for Mac.
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(use-package dash-at-point
:commands (dash-at-point)
:general (:states 'normal "gD" 'dash-at-point))
#+END_SRC
#+end_src
Im 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
#+begin_src emacs-lisp
(use-package devdocs
:after evil
:general (:states 'normal "gD" 'devdocs-lookup)
@ -120,11 +117,11 @@ Im interested in using [[https://devdocs.io/][devdocs]] instead, which is sim
"d u" '("update" . devdocs-update-all)
"d x" '("uninstall" . devdocs-delete)
"d s" '("search" . devdocs-search)))
#+END_SRC
#+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
#+begin_src emacs-lisp :tangle no
(use-package devdocs-browser
:general (:states 'normal "gD" 'devdocs-browser-open)
@ -140,10 +137,10 @@ The [[https://github.com/blahgeek/emacs-devdocs-browser][devdocs-browser]] proje
"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
#+end_src
** Code Folding
While Emacs has many options for viewing and moving around code, sometimes, it is nice to /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
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
@ -151,14 +148,14 @@ While Emacs has many options for viewing and moving around code, sometimes, it i
hs-hide-initial-comment-block t
hs-isearch-open t)
:hook (prog-mode . hs-minor-mode))
#+END_SRC
However, hide-show doesnt 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 really well.
#+BEGIN_SRC emacs-lisp
#+end_src
Note that =hide-show= doesnt 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
#+end_src
To take advantage of this, type:
- ~z m~ :: To collapse everything
- ~z r~ :: To open everything
@ -171,7 +168,7 @@ Note: Yes, we could use [[https://github.com/mrkkrp/vimish-fold][vimish-fold]] (
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
#+begin_src emacs-lisp
(use-package lsp-mode
:commands lsp
:init
@ -181,12 +178,12 @@ Instead, we install [[https://emacs-lsp.github.io/lsp-mode/][LSP Mode]] (and fri
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
#+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
#+begin_src emacs-lisp
(use-package lsp-ui
:commands lsp-ui-mode
:config
@ -194,15 +191,15 @@ The [[https://github.com/emacs-lsp/lsp-ui][lsp-ui]] project offers much of the d
lsp-ui-sideline-show-hover t
lsp-ui-sideline-show-diagnostics t)
:hook (lsp-mode . lsp-ui-mode))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package company-lsp
:config
(push 'company-lsp company-backends))
#+END_SRC
#+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.
@ -210,7 +207,7 @@ To options that might be interesting:
*** 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
#+begin_src emacs-lisp
(use-package lsp-ui-imenu
:straight nil
:after lsp-ui
@ -219,35 +216,35 @@ The [[https://github.com/emacs-lsp/lsp-ui/blob/master/lsp-ui-imenu.el][lsp-imenu
"g" '(:ignore t :which-key "goto")
"g m" '("imenu" . lsp-ui-imenu))
(add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package lsp-treemacs
:config
(ha-prog-leader
"0" '("treemacs" . lsp-treemacs-symbols)))
#+END_SRC
#+end_src
*** Origami Folding
The [[https://github.com/emacs-lsp/lsp-origami][lsp-origami]] project integrates the [[https://github.com/gregsexton/origami.el][origami]] project with LSP for /better code folding/:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package lsp-origami
:hook (lsp-after-open . lsp-origami-try-enable))
#+END_SRC
#+end_src
*** Debugging
Do we want to use a debugger?
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(use-package dap-mode
:init
(require 'dap-python))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package alert
:init
(setq alert-default-style
@ -265,20 +262,20 @@ As I've mentioned [[http://www.howardism.org/Technical/Emacs/beep-for-emacs.html
compile
shell-command))
(advice-add func :around #'beep-when-runs-too-long)))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package iedit
:config
(ha-leader "s e" '("iedit" . iedit-mode)))
#+END_SRC
#+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
#+begin_src emacs-lisp
(defun ha-comment-line (&optional start end)
(interactive "r")
(when (or (null start) (not (region-active-p)))
@ -293,14 +290,14 @@ I like =comment-dwim= (~M-;~), and I like =comment-box=, but I have an odd perso
(insert "\n------------------------------------------------------------------------")
(comment-region (point-min) (point-max))
(widen)))
#+END_SRC
#+end_src
And a keybinding:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-prog-leader "c" '("comment line" . ha-comment-line))
#+END_SRC
#+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
#+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
@ -315,10 +312,10 @@ While I like [[help:eval-print-last-sexp][eval-print-last-sexp]], I would like a
(dotimes (i 2)
(next-line)
(join-line)))
#+END_SRC
#+end_src
Typical keybindings for all programming modes:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-prog-leader
"e" '(:ignore t :which-key "eval")
"e ;" '("expression" . eval-expression)
@ -327,10 +324,10 @@ Typical keybindings for all programming modes:
"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
#+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
#+begin_src emacs-lisp
(defun ha-prettify-prog ()
"Extends the `prettify-symbols-alist' for programming."
(mapc (lambda (pair) (push pair prettify-symbols-alist))
@ -341,83 +338,83 @@ The idea of using math symbols for a programming languages keywords is /cute/, b
(prettify-symbols-mode))
(add-hook 'prog-mode-hook 'ha-prettify-prog)
#+END_SRC
#+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 dont 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
#+begin_src emacs-lisp :tangle no
(setq ivy-taskrunner-notifications-on t
ivy-taskrunner-doit-bin-path "/usr/local/bin/doit")
#+END_SRC
#+end_src
Doom provides basic support, but we need more keybindings:
#+BEGIN_SRC emacs-lisp :tangle no
#+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
#+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
#+begin_src python :tangle no
def hello():
"""This command greets you."""
return {
'actions': [ 'echo hello' ],
}
#+END_SRC
#+end_src
** Display Configuration
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package doom-modeline
:config
(setq doom-modeline-lsp t
doom-modeline-env-version t))
#+END_SRC
#+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
#+begin_src emacs-lisp
(use-package yaml-mode
:mode (rx ".y" (optional "a") "ml" string-end))
#+END_SRC
#+end_src
Ansible uses Jinja, so we install the [[https://github.com/paradoxxxzero/jinja2-mode][jinja2-mode]]:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package jinja2-mode
:mode (rx ".j2" string-end))
#+END_SRC
#+end_src
Do I consider all YAML files an Ansible file needing [[https://github.com/k1LoW/emacs-ansible][ansible-mode]]?
#+BEGIN_SRC emacs-lisp
#+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
#+end_src
The [[help:ansible-vault-password-file][ansible-vault-password-file]] variable needs to change /per project/, so lets use the =.dir-locals.el= file, for instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
((nil . ((ansible-vault-password-file . "playbooks/.vault-password"))))
#+END_SRC
#+end_src
However, lets have all YAML files able to access Ansibles documentation using the [[https://github.com/emacsorphanage/ansible-doc][ansible-doc]] project:
#+BEGIN_SRC emacs-lisp
#+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
#+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
#+begin_src emacs-lisp
(use-package polymode)
(use-package poly-ansible
@ -425,7 +422,7 @@ The [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project uses [
:straight (:host github :repo "emacsmirror/poly-ansible")
:hook ((yaml-mode . poly-ansible-mode)
(poly-ansible-mode . font-lock-update)))
#+END_SRC
#+end_src
** Shell Scripts
@ -433,7 +430,7 @@ 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
#+begin_src emacs-lisp
(use-package sh-mode
:straight (:type built-in)
:mode (rx (or (seq ".sh" eol)
@ -443,24 +440,23 @@ While filename extensions work fine most of the time, I don't like to pre-pend =
"/bin/")) "sh-mode.sh")
:hook
(after-save . executable-make-buffer-file-executable-if-script-p))
#+END_SRC
#+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
#+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
#+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
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~

View file

@ -1,15 +1,14 @@
#+TITLE: Remote Access to Systems
#+AUTHOR: Howard X. Abrams
#+DATE: 2020-09-25
#+FILETAGS: :emacs:
A literate configuration for accessing remote systems.
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
;;; ha-remoting --- Accessing remote systems. -*- lexical-binding: t; -*-
;;
;; © 2020-2022 Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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>
@ -23,14 +22,14 @@ A literate configuration for accessing remote systems.
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+end_src
* Local Terminals
The following section configures my Terminal experience, both inside and outside Emacs.
** Eshell
I used to use [[http://www.howardism.org/Technical/Emacs/eshell.html][Eshell all the time]], but now I've migrated most of /work/ directly into Emacs (rewriting all those shell scripts a Emacs Lisp code). However, a shell is pretty good for my brain at organizing files (old habits, maybe).
I used to use [[http://www.howardism.org/Technical/Emacs/eshell.html][Eshell all the time]], but now I've migrated most of /work/ directly into Emacs (rewriting all those shell scripts a Emacs Lisp code). A shell is pretty good for my brain at organizing files (old habits, maybe).
First, my /aliases/ follow me around, and the following creates the alias file, =~/.emacs.d/eshell/alias=:
#+BEGIN_SRC shell :tangle (identity eshell-aliases-file) :mkdirp yes
#+begin_src shell :tangle (identity eshell-aliases-file) :mkdirp yes
alias ll ls -l $*
alias clear recenter 0
alias emacs 'find-file $1'
@ -38,10 +37,10 @@ First, my /aliases/ follow me around, and the following creates the alias file,
alias ee 'find-file-other-window $1'
alias grep 'rg -n -H --no-heading -e "$*"'
alias find 'echo Please use fd instead.' # :-)
#+END_SRC
#+end_src
Since that file already exists (probably), the following command may not be necessary:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package eshell
:custom
(eshell-kill-on-exit t)
@ -49,11 +48,11 @@ Since that file already exists (probably), the following command may not be nece
:hook
(eshell-exit . (lambda () (delete-window))))
#+END_SRC
#+end_src
I usually want a new window running Eshell, that is smaller than the current buffer:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun eshell-there (parent)
"Open an eshell session in a PARENT directory
in a smaller window named after this directory."
@ -68,10 +67,10 @@ I usually want a new window running Eshell, that is smaller than the current buf
(insert (concat "ls"))
(eshell-send-input)))
#+END_SRC
#+end_src
We either want to start the shell in the same parent as the current buffer, or at the root of the project:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun eshell-here ()
"Opens up a new shell in the directory associated with the
current buffer's file. Rename the eshell buffer name to match
@ -85,10 +84,10 @@ We either want to start the shell in the same parent as the current buffer, or a
"Open a new shell in the project root directory, in a smaller window."
(interactive)
(eshell-there (projectile-project-root)))
#+END_SRC
#+end_src
Add my org-specific predicates, see this [[http://www.howardism.org/Technical/Emacs/eshell-fun.html][this essay]] for the details:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun eshell-org-file-tags ()
"Helps the eshell parse the text the point is currently on,
looking for parameters surrounded in single quotes. Returns a
@ -119,7 +118,7 @@ Add my org-specific predicates, see this [[http://www.howardism.org/Technical/Em
(defvar eshell-predicate-alist nil
"A list of predicates than can be applied to a globbing pattern.")
(add-to-list 'eshell-predicate-alist '(?T . (eshell-org-file-tags)))
#+END_SRC
#+end_src
* Remote Terminals
Sure =iTerm= is nice for connecting and running commands on remote systems, however, it lacks a command line option that allows you to select and manipulate the displayed text without a mouse. This is where Emacs can shine.
@ -129,9 +128,9 @@ When calling the =ha-ssh= function, it opens a =vterm= window which, unlike othe
Preload a list of favorite/special hostnames with multiple calls to:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(ha-ssh-add-favorite-host "Devbox 42" "10.0.1.42")
#+END_SRC
#+end_src
Then calling =ha-ssh= function, a list of hostnames is available to quickly jump on a system (with the possibility of fuzzy matching if you have Helm or Ivy installed).
@ -145,14 +144,14 @@ Use the /favorite host/ list to quickly edit a file on a remote system using Tra
Working with remote shell connections programmatically, for instance:
#+BEGIN_SRC emacs-lisp :tangle no
#+begin_src emacs-lisp :tangle no
(let ((win-name "some-host"))
(ha-ssh "some-host.in.some.place" win-name)
(ha-ssh-send "source ~/.bash_profile" win-name)
(ha-ssh-send "clear" win-name))
;; ...
(ha-ssh-exit win-name)
#+END_SRC
#+end_src
Actually the =win-name= in this case is optional, as it will use a good default.
@ -163,7 +162,7 @@ I'm not giving up on Eshell, but I am playing around with [[https://github.com/a
VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previous word, and yeah, I want to make sure that both keystrokes do the same thing.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package vterm
:init
(setq vterm-shell "/usr/local/bin/fish")
@ -178,7 +177,7 @@ VTerm has an issue (at least for me) with ~M-Backspace~ not deleting the previou
(lambda () (interactive) (vterm-send-key (kbd "C-w")))))
(advice-add 'vterm-copy-mode :after 'evil-normal-state))
#+END_SRC
#+end_src
The advantage of running terminals in Emacs is the ability to copy text without a mouse. For that, hit ~C-c C-t~ to enter a special copy-mode. If I go into this mode, I might as well also go into normal mode to move the cursor.
@ -188,7 +187,7 @@ Hrm. Seems that I might want a function to copy the output of the last command t
** Variables
Let's begin by defining some variables used for communication between the functions.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defvar ha-latest-ssh-window-name nil
"The window-name of the latest ssh session. Most commands default to the last session.")
@ -197,19 +196,19 @@ Let's begin by defining some variables used for communication between the functi
(defvar ha-ssh-favorite-hostnames '()
"A list of tuples (associate list) containing a hostname and its IP address.
See =ha-ssh-add-favorite-host= for easily adding to this list.")
#+END_SRC
#+end_src
Also, let's make it easy for me to change my default shell:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defvar ha-ssh-shell (shell-command-to-string "type -p fish")
"The executable to the shell I want to use locally.")
#+END_SRC
#+end_src
** Interactive Interface to Remote Systems
The function, =ha-ssh= pops up a list of /favorite hosts/ and then uses the =vterm= functions to automatically SSH into the chosen host:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh (hostname &optional window-name)
"Start a SSH session to a given HOSTNAME (with an optionally specified WINDOW-NAME).
If called interactively, it presents the user with a list
@ -228,11 +227,11 @@ returned by =ha-ssh-choose-host=."
(vterm-send-return))
(pop-to-buffer ha-latest-ssh-window-name))
#+END_SRC
#+end_src
Of course, we need a function that =interactive= can call to get that list, and my thought is to call =helm= if it is available, otherwise, assume that ido/ivy will take over the =completing-read= function:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-choose-host ()
"Prompts the user for a host, and if it is in the cache, return
its IP address, otherwise, return the input given.
@ -246,11 +245,11 @@ This is used in calls to =interactive= to select a host."
:fuzzy t :history ha-ssh-host-history)
(completing-read "Hostname: " ha-ssh-favorite-hostnames nil 'confirm nil 'ha-ssh-host-history))))
(alist-get hostname ha-ssh-favorite-hostnames hostname nil 'equal)))
#+END_SRC
#+end_src
Simply calling =vterm= fails to load my full environment, so this allows me to start the terminal in a particular directory (defaulting to the root of the current project):
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-shell (&optional directory)
"Creates and tidies up a =vterm= terminal shell in side window."
(interactive (list (read-directory-name "Starting Directory: " (projectile-project-root))))
@ -264,22 +263,22 @@ Simply calling =vterm= fails to load my full environment, so this allows me to s
;; (ha-ssh-send "source ~/.bash_profile" buf-name)
;; (ha-ssh-send "clear" buf-name)
)))
#+END_SRC
#+end_src
Before we leave this section, I realize that I would like a way to /add/ to my list of hosts:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-add-favorite-host (hostname ip-address)
"Add a favorite host to your list for easy pickin's."
(interactive "sHostname: \nsIP Address: ")
(add-to-list 'ha-ssh-favorite-hostnames (cons hostname ip-address)))
#+END_SRC
#+end_src
** Programmatic Interface
The previous functions (as well as my own end of sprint demonstrations) often need to issue some commands to a running terminal session, which is a simple wrapper around a /send text/ and /send return/ sequence:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-send (phrase &optional window-name)
"Send command PHRASE to the currently running SSH instance.
If you want to refer to another session, specify the correct WINDOW-NAME.
@ -296,11 +295,11 @@ This is really useful for scripts and demonstrations."
(progn
(term-send-raw-string phrase)
(term-send-input))))
#+END_SRC
#+end_src
On the rare occasion that I write a shell script, or at least, need to execute some one-line shell commands from some document, I have a function that combines a /read line from buffer/ and then send it to the currently running terminal:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-send-line ()
"Copy the contents of the current line in the current buffer,
and call =ha-ssh-send= with it. After sending the contents, it
@ -314,11 +313,11 @@ returns to the current line."
(ha-ssh-send trim-cmd)
;; (sit-for 0.25)
(pop-to-buffer buf)))
#+END_SRC
#+end_src
Let's have a quick way to bugger out of the terminal:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-exit (&optional window-name)
"End the SSH session specified by WINDOW-NAME (or if not, the latest session)."
(interactive)
@ -331,13 +330,13 @@ Let's have a quick way to bugger out of the terminal:
(term-send-eof))
(kill-buffer window-name)
(delete-window))
#+END_SRC
#+end_src
** Editing Remote Files
TRAMP, when it works, is amazing that we can give it a reference to a remote directory, and have =find-file= magically autocomplete.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-find-file (hostname)
"Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=."
(interactive (list (ha-ssh-choose-host)))
@ -351,18 +350,18 @@ TRAMP, when it works, is amazing that we can give it a reference to a remote dir
(if other-window
(find-file-other-window tramp-file)
(find-file tramp-file))))
#+END_SRC
#+end_src
We can even edit it as root:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-find-root (hostname)
"Constructs a ssh-based, tramp-focus, file reference, and then calls =find-file=."
(interactive (list (ha-ssh-choose-host)))
(let ((tramp-ssh-ref (format "/ssh:%s|sudo:%s:" hostname hostname))
(other-window (when (equal current-prefix-arg '(4)) t)))
(ha-ssh--find-file tramp-ssh-ref other-window)))
#+END_SRC
#+end_src
** OpenStack Interface
@ -370,20 +369,20 @@ Instead of making sure I have a list of remote systems already in the favorite h
We'll give =openstack= CLI a =--format json= option to make it easier for parsing:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(use-package json)
#+END_SRC
#+end_src
Need a variable to hold all our interesting hosts. Notice I use the word /overcloud/, but this is a name I've used for years to refer to /my virtual machines/ that I can get a listing of, and not get other VMs that I don't own.
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defvar ha-ssh-overcloud-cache-data nil
"A vector of associated lists containing the servers in an Overcloud.")
#+END_SRC
#+end_src
If our cache data is empty, we could automatically retrieve this information, but only on the first time we attempt to connect. To do this, we'll =advice= the =ha-ssh-choose-host= function defined earlier:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-overcloud-query-for-hosts ()
"If the overcloud cache hasn't be populated, ask the user if we want to run the command."
(when (not ha-ssh-overcloud-cache-data)
@ -391,11 +390,11 @@ If our cache data is empty, we could automatically retrieve this information, bu
(call-interactively 'ha-ssh-overcloud-cache-populate))))
(advice-add 'ha-ssh-choose-host :before 'ha-ssh-overcloud-query-for-hosts)
#+END_SRC
#+end_src
We'll do the work of getting the /server list/ with this function:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-overcloud-cache-populate (cluster)
"Given an `os-cloud' entry, stores all available hostnames.
Calls `ha-ssh-add-favorite-host' for each host found."
@ -412,21 +411,21 @@ We'll do the work of getting the /server list/ with this function:
(alist-get 'cedev13)
(seq-first))))
(message "Call to `openstack' complete. Found %d hosts." (length json-data))))
#+END_SRC
#+end_src
In case I change my virtual machines, I can repopulate that cache:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-overcloud-cache-repopulate ()
"Repopulate the cache based on redeployment of my overcloud."
(interactive)
(setq ha-ssh-overcloud-cache-data nil)
(call-interactively 'ha-ssh-overcloud-cache-populate))
#+END_SRC
#+end_src
The primary interface:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(defun ha-ssh-overcloud (hostname)
"Log into an overcloud host given by HOSTNAME. Works better if
you have previously run =ssh-copy-id= on the host. Remember, to
@ -446,11 +445,11 @@ Emacs), hit =C-c C-k=."
(ha-ssh-send (format "export PS1='\\[\\e[34m\\]%s\\[\e[m\\] \\[\\e[33m\\]\\$\\[\\e[m\\] '"
window-label))
(ha-ssh-send "clear")))
#+END_SRC
#+end_src
* Keybindings
This file, so far, as been good-enough for a Vanilla Emacs installation, but to hook into Doom's leader for some sequence binding, this code isn't:
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
(ha-leader
"a e" '("eshell" . eshell-here)
"a E" '("top eshell" . eshell-project)
@ -463,15 +462,15 @@ This file, so far, as been good-enough for a Vanilla Emacs installation, but to
"a s q" '("quit shell" . ha-ssh-exit)
"a s f" '("find-file" . ha-ssh-find-file)
"a s r" '("find-root" . ha-ssh-find-root))
#+END_SRC
#+end_src
* Technical Artifacts :noexport:
Provide a name so we can =require= the file:
#+BEGIN_SRC emacs-lisp :exports none
#+begin_src emacs-lisp :exports none
(provide 'ha-remoting)
;;; ha-remoting.el ends here
#+END_SRC
#+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~

View file

@ -14,23 +14,25 @@
${2:A literate programming file configuring Emacs.}
#+BEGIN_SRC emacs-lisp :exports none
;;; `(file-name-base (buffer-file-name)))`.el --- $2 -*- lexical-binding: t; -*-
;;
;; Copyright (C) `(format-time-string "%Y")` `user-full-name`
;;
;; Author: `user-full-name` <http://gitlab.com/howardabrams>
;; Maintainer: `user-full-name` <`user-mail-address`>
;; Created: `(format-time-string "%B %e, %Y")`
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; `(buffer-file-name)`
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+begin_src emacs-lisp :exports none
;;; `(file-name-base (buffer-file-name)))`.el --- $2 -*- lexical-binding: t; -*-
;;
;; © `(format-time-string "%Y")` `user-full-name`
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: `user-full-name` <http://gitlab.com/howardabrams>
;; Maintainer: `user-full-name` <`user-mail-address`>
;; Created: `(format-time-string "%B %e, %Y")`
;;
;; While obvious, GNU Emacs does not include this file
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; `(buffer-file-name)`
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
@ -40,12 +42,11 @@ $0
Let's provide a name so that the file can be required:
#+BEGIN_SRC emacs-lisp :exports none
(provide '`(file-name-base (buffer-file-name)))`)
;;; `(file-name-base (buffer-file-name)))`.el ends here
#+END_SRC
#+begin_src emacs-lisp :exports none
(provide '`(file-name-base (buffer-file-name)))`)
;;; `(file-name-base (buffer-file-name)))`.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: $2

View file

@ -2,6 +2,6 @@
# name: code-block
# key: <s
# --
#+BEGIN_SRC $1
#+begin_src $1
$0
#+END_SRC
#+end_src

View file

@ -2,6 +2,6 @@
# name: emacs-lisp-code
# key: <sl
# --
#+BEGIN_SRC emacs-lisp
#+begin_src emacs-lisp
$0
#+END_SRC
#+end_src

View file

@ -2,6 +2,6 @@
# name: example-block
# key: <e
# --
#+BEGIN_EXAMPLE
#+begin_example
$0
#+END_EXAMPLE
#+end_example

View file

@ -3,10 +3,10 @@
# key: <g
# --
#+BEGIN_SRC dot :file ${1:`(file-name-base (buffer-file-name)))`-`(ha-org-next-image-number)`}.${2:png} :exports file :results file
#+begin_src dot :file ${1:`(file-name-base (buffer-file-name)))`-`(ha-org-next-image-number)`}.${2:png} :exports file :results file
digraph G {
${0:A -> B};
}
#+END_SRC
#+end_src
#+ATTR_ORG: :width 800px
#+attr_org: :width 800px

View file

@ -2,10 +2,10 @@
# name: header
# key: header
# --
#+TITLE: ${1:`(replace-regexp-in-string "-" " " (capitalize (file-name-nondirectory (file-name-sans-extension (buffer-file-name)))))`}
#+AUTHOR: `(user-full-name)`
#+EMAIL: `user-mail-address`
#+DATE: `(format-time-string "%Y-%m-%d %B")`
#+TAGS: $2
#+title: ${1:`(replace-regexp-in-string "-" " " (capitalize (file-name-nondirectory (file-name-sans-extension (buffer-file-name)))))`}
#+author: `(user-full-name)`
#+email: `user-mail-address`
#+date: `(format-time-string "%Y-%m-%d %B")`
#+tags: $2
$0

View file

@ -2,4 +2,4 @@
# name: name
# key: name
# --
#+NAME: ${0}
#+name: ${0}

View file

@ -5,12 +5,12 @@
# group: plantuml
# --
#+BEGIN_SRC plantuml :file ${1:`(ha-org-next-image-number)`}.${2:png} :exports file :results file
#+begin_src plantuml :file ${1:`(ha-org-next-image-number)`}.${2:png} :exports file :results file
@startuml
!include https://raw.githubusercontent.com/ptrkcsk/one-dark-plantuml-theme/v1.0.0/theme.puml
$0
@enduml
#+END_SRC
#+end_src
#+ATTR_ORG: :width 800px
#+attr_org: :width 800px
[[file:$1.$2]]

View file

@ -2,6 +2,6 @@
# name: section-property
# key: prop
# --
:PROPERTIES:
:properties:
:header-args:${1:emacs-lisp} :${2:results} ${3:silent}
:END:
:end:

View file

@ -2,6 +2,6 @@
# name: shell-script-code
# key: <ss
# --
#+BEGIN_SRC sh
#+begin_src sh
$0
#+END_SRC
#+end_src

View file

@ -2,4 +2,4 @@
# name: title
# key: title
# --
#+TITLE: ${0}
#+title: ${0}

View file

@ -1,15 +1,14 @@
;;; `(file-name-nondirectory (buffer-file-name))` --- $1 -*- lexical-binding: t; -*-
;;
;; © `(format-time-string "%Y")` Howard X. Abrams
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; 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: `(format-time-string "%e %B %Y")`
;;
;; This file is not part of GNU Emacs. Obviously. But you knew that.
;; Obviously, GNU Emacs does not include this file in its distribution.
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by

View file

@ -9,38 +9,37 @@
A literate programming file for ${2:configuring Emacs.}
#+BEGIN_SRC emacs-lisp :exports none
;;; `(file-name-base (buffer-file-name)))` --- $2 -*- lexical-binding: t; -*-
;;
;; © `(format-time-string "%Y")` `user-full-name`
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: `user-full-name` <http://gitlab.com/howardabrams>
;; Maintainer: `user-full-name`
;; Created: `(format-time-string "%B %e, %Y")`
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; `(buffer-file-name)`
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
#+begin_src emacs-lisp :exports none
;;; `(file-name-base (buffer-file-name)))` --- $2 -*- lexical-binding: t; -*-
;;
;; © `(format-time-string "%Y")` `user-full-name`
;; This work is licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: `user-full-name` <http://gitlab.com/howardabrams>
;; Maintainer: `user-full-name`
;; Created: `(format-time-string "%B %e, %Y")`
;;
;; While obvious, GNU Emacs does not include this file or project.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; `(buffer-file-name)`
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
$0
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
(provide '`(file-name-base (buffer-file-name)))`)
;;; `(file-name-base (buffer-file-name)))`.el ends here
#+END_SRC
#+begin_src emacs-lisp :exports none
(provide '`(file-name-base (buffer-file-name)))`)
;;; `(file-name-base (buffer-file-name)))`.el ends here
#+end_src
#+DESCRIPTION: $2