2021-11-02 00:27:14 +00:00
#+TITLE : General Org-Mode Configuration
#+AUTHOR : Howard X. Abrams
#+DATE : 2020-09-18
A literate programming file for configuring org-mode and those files.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; ha --- Org configuration. -*- lexical-binding: t; -* -
;;
2023-02-23 17:35:36 +00:00
;; © 2020-2023 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
;; Maintainer: Howard X. Abrams
;; Created: September 18, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/ha-org.org
;; And tangle the file to recreate this one.
;;
;;; Code:
2021-11-14 06:18:19 +00:00
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-17 18:32:02 +00:00
* Use Package
Org is a /large/ complex beast with a gazillion settings, so I discuss these later in this document.
2023-07-05 16:25:01 +00:00
#+begin_src emacs-lisp :noweb yes
2021-11-17 18:32:02 +00:00
(use-package org
2023-05-25 17:26:34 +00:00
;; TODO: Using the latest org-mode
;; :straight (:type built-in)
2022-02-17 17:50:34 +00:00
:mode ("\\.org" . org-mode) ; Addresses an odd warning
2021-11-17 18:32:02 +00:00
:init
<<variables >>
<<org-todo >>
2023-02-22 01:26:05 +00:00
<<org-todo-clock >>
2022-03-09 18:45:37 +00:00
<<ob-configuration >>
2023-03-13 17:54:51 +00:00
<<html-exporting >>
2021-11-24 00:34:48 +00:00
2021-11-17 18:32:02 +00:00
:config
2021-11-24 00:34:48 +00:00
<<ha-org-leader >>
2021-11-17 18:32:02 +00:00
<<visual-hook >>
<<text-files >>
<<org-font-lock >>
<<no-flycheck-in-org >>
<<ob-languages >>
2022-03-09 18:45:37 +00:00
<<ob-graphviz >>
2021-11-24 00:34:48 +00:00
<<ox-exporters >>
2021-11-17 18:32:02 +00:00
<<org-return-key >>
<<global-keybindings >>
2021-11-18 17:40:12 +00:00
<<org-keybindings >>)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Initialization Section
2022-03-09 06:01:19 +00:00
Begin by initializing these org variables:
2022-06-18 00:25:47 +00:00
#+name : variables
#+begin_src emacs-lisp :tangle no
2021-11-02 00:27:14 +00:00
(setq org-return-follows-link t
2021-11-14 06:18:19 +00:00
org-adapt-indentation nil ; Don't physically change files
org-startup-indented t ; Visually show paragraphs indented
org-list-indent-offset 2
org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0,
; but uses a trick to make it
; appear indented.
2022-03-11 18:55:39 +00:00
org-imenu-depth 4
2022-02-02 19:48:19 +00:00
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
2021-11-02 00:27:14 +00:00
2023-03-18 02:24:04 +00:00
org-html-validation-link nil
2022-02-02 19:48:19 +00:00
org-export-with-sub-superscripts nil
2023-03-13 17:54:51 +00:00
org-html-validation-link nil
2021-11-02 00:27:14 +00:00
org-directory "~/personal"
org-default-notes-file "~/personal/general-notes.txt"
2022-03-09 06:01:19 +00:00
org-enforce-todo-dependencies t ; Can't close a task without completed subtasks
2021-11-02 00:27:14 +00:00
org-agenda-dim-blocked-tasks t
org-log-done 'time
org-completion-use-ido t
org-outline-path-complete-in-steps nil
org-src-tab-acts-natively t
org-agenda-span 'day ; Default is 'week
2021-11-06 00:07:33 +00:00
org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t)
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-03 23:16:50 +00:00
2021-11-02 00:27:14 +00:00
* Configuration Section
I pretend that my org files are word processing files that wrap automatically:
2022-06-18 00:25:47 +00:00
#+name : visual-hook
#+begin_src emacs-lisp :tangle no
2021-11-02 00:27:14 +00:00
(add-hook 'org-mode-hook #'visual-line-mode)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-17 18:32:02 +00:00
2021-11-02 00:27:14 +00:00
Files that end in =.txt= are still org files to me:
2022-06-18 00:25:47 +00:00
#+name : text-files
#+begin_src emacs-lisp :tangle no
2021-11-17 18:32:02 +00:00
(add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
2021-11-02 00:27:14 +00:00
2021-11-17 18:32:02 +00:00
(add-to-list 'safe-local-variable-values '(org-content . 2))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
*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.
2021-11-02 00:27:14 +00:00
** 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.
2022-03-09 06:01:19 +00:00
We begin with the interactive function that calls our code if we are at the end of the line.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-12 05:05:41 +00:00
(defun ha-org-return ()
2021-11-02 00:27:14 +00:00
"If at the end of a line, do something special based on the
2021-11-12 05:05:41 +00:00
information about the line by calling `ha-org-special-return',
2022-03-09 06:01:19 +00:00
otherwise, `org-return' as usual."
2021-11-02 00:27:14 +00:00
(interactive)
(if (eolp)
2021-11-12 05:05:41 +00:00
(ha-org-special-return)
2021-11-02 00:27:14 +00:00
(org-return)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
And bind it to the Return key:
2022-06-18 00:25:47 +00:00
#+name : org-return-key
#+begin_src emacs-lisp :tangle no
2021-11-12 05:05:41 +00:00
(define-key org-mode-map (kbd "RET") #'ha-org-return)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
What should we do if we are at the end of a line?
2022-03-09 06:01:19 +00:00
- Given a prefix, call =org-return= as usual in an org file.
- On a link, call =org-return= and open it.
- On a header? Create a new header.
- In a table? Create a new row.
- In a list, create a new item.
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
I should break this function into smaller bits ...
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-12 05:05:41 +00:00
(defun ha-org-special-return (&optional ignore)
2022-09-02 23:08:58 +00:00
"Add new list item with RET.
2021-11-06 00:07:33 +00:00
A double return on an empty element deletes it.
Use a prefix arg to get regular RET."
(interactive "P")
(if ignore
(org-return)
(cond
;; Open links like usual
((eq 'link (car (org-element-context)))
(org-return))
((and (org-really-in-item-p) (not (bolp)))
(if (org-element-property :contents-begin (org-line-element-context))
(progn
(end-of-line)
(org-insert-item))
(delete-region (line-beginning-position) (line-end-position))))
(t
(org-return)))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-09 06:01:19 +00:00
(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
point could actually be in some =code= words, but still be on an
item element."
(save-excursion
(let ((location (org-element-property :contents-begin (org-line-element-context))))
(when location
(goto-char location))
(org-in-item-p))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(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))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Tasks
2023-02-22 01:26:05 +00:00
I need to add a /blocked/ state, and wouldn’ t /doing/ be better than /in progress/ (you know, without a space):
2022-06-18 00:25:47 +00:00
#+name : org-todo
#+begin_src emacs-lisp :tangle no
2021-12-29 19:09:43 +00:00
(setq org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)")
2021-11-02 00:27:14 +00:00
(sequence "BLOCKED(b)" "|" "CANCELLED(c)")))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2023-02-22 01:26:05 +00:00
[[https://janusworx.com/blog/what-i-learned-today-2023-02-10/ ][Mario Braganza ]] had an interesting idea of starting the clock when a task changes to /in progress/ :
#+name : org-todo-clock
#+begin_src emacs-lisp
(defun ha-org-clock-todo-change ()
"Called from hook `org-after-todo-state-change-hook'.
Clock in if a task changes to DOING (i.e. IN_PROGRESS),
and clocks out with any other state change."
(if (string= org-state "DOING")
(org-clock-in)
(org-clock-out-if-current)))
(add-hook 'org-after-todo-state-change-hook 'ha-org-clock-todo-change)
#+end_src
2021-11-02 00:27:14 +00:00
And I would like to have cute little icons for those states:
2022-06-18 00:25:47 +00:00
#+name : org-font-lock
#+begin_src emacs-lisp
2022-03-09 06:01:19 +00:00
(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
(1 (progn (compose-region (match-beginning 1) (match-end 1) "⚑") nil)))
("^\\*+ \\(DOING\\) "
(1 (progn (compose-region (match-beginning 1) (match-end 1) "⚐") nil)))
("^\\*+ \\(CANCELED\\) "
(1 (progn (compose-region (match-beginning 1) (match-end 1) "✘") nil)))
("^\\*+ \\(BLOCKED\\) "
(1 (progn (compose-region (match-beginning 1) (match-end 1) "✋") nil)))
("^\\*+ \\(DONE\\) "
(1 (progn (compose-region (match-beginning 1) (match-end 1) "✔") nil)))
;; Here is my approach for making the initial asterisks for listing items and
;; whatnot, appear as Unicode bullets ;; (without actually affecting the text
;; file or the behavior).
("^ +\\([-*]\\) "
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Meetings
2022-03-09 06:01:19 +00:00
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.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(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."
(interactive)
(outline-mark-subtree) ; Select org-mode section
2022-03-09 06:01:19 +00:00
(narrow-to-region (region-beginning) (region-end)) ; Show that region
2021-11-02 00:27:14 +00:00
(deactivate-mark)
(delete-other-windows) ; remove other windows
(text-scale-set 2) ; readable by others
(fringe-mode 0)
(message "When finished taking your notes, run meeting-done."))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
Of course, I need an 'undo' feature when the meeting is over…
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(defun meeting-done ()
"Attempt to 'undo' the effects of taking meeting notes."
(interactive)
(widen) ; Opposite of narrow-to-region
(text-scale-set 0) ; Reset the font size increase
(fringe-mode 1)
(winner-undo)) ; Put the windows back in place
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-09 15:58:18 +00:00
** Searching
2023-01-16 17:48:10 +00:00
Came up with a great way to search a project for Org-specific files, and wrote [[https://howardism.org/Technical/Emacs/org-find-file.html ][an essay ]] describing the approach and the code. The idea is that I can call =find-file= , but the list of files is not only the filename, but the Org =#+title:= as well as any tags located in the file.
2022-07-05 23:08:47 +00:00
#+begin_src emacs-lisp
2023-01-16 17:48:10 +00:00
(use-package org-find-file
:straight nil
:config
(ha-leader "f o" '("load org" . org-find-file)))
2022-07-05 23:08:47 +00:00
#+end_src
2023-01-16 17:48:10 +00:00
Now that my paragraphs in an org file are on a single line, I could use =rg= (or some other =grep= program), 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 than line-oriented search systems. Let’ s create operating-system functions the command line for searching:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-09 15:58:18 +00:00
(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)."
(let ((paths (if (listp path)
(mapconcat (lambda (p) (concat "-onlyin " p)) path " ")
(concat "-onlyin " path))))
(format "mdfind %s -interpret %s" paths phrase)))
(defun ha-search-notes--linux (phrase path)
"Return the indexed search system command on Linux, recoll.
Including the parameters using the PHRASE on the PATH(s)."
(format "recoll -t -a -b %s" phrase))
2022-07-05 23:08:47 +00:00
#+end_src
2022-04-09 15:58:18 +00:00
2022-07-05 23:08:47 +00:00
And let’ s see how that works:
#+begin_src emacs-lisp :tangle no :results replace
(ha-search-notes--macos "crossway stream" "~/Notes")
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-09 15:58:18 +00:00
2022-07-05 23:08:47 +00:00
This function calls the above-mentioned operating-system-specific functions, but returns the matching files as a /single string/ (where single quotes wrap each file, and all joined together, separated by spaces). This function also allows me to /not-match/ backup files and whatnot.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-09 15:58:18 +00:00
(defun ha-search-notes--files (phrase path)
"Return an escaped string of all files matching PHRASE.
2022-07-05 23:08:47 +00:00
On a Mac, the PATH limits the scope of the search."
2022-04-09 15:58:18 +00:00
(let ((command (if (equal system-type 'darwin)
(ha-search-notes--macos phrase path)
(ha-search-notes--linux phrase path))))
(->> command
2022-10-18 03:49:00 +00:00
(shell-command-to-list)
2022-04-09 15:58:18 +00:00
(--remove (s-matches? "~$" it))
(--remove (s-matches? "#" it))
(--map (format "'%s'" it))
(s-join " "))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-07-05 23:08:47 +00:00
Let’ s see it in action:
#+begin_src emacs-lisp :tangle no :results replace
2022-07-07 16:26:55 +00:00
(ha-search-notes--files "openstack grafana" '("~/Notes"))
2022-07-05 23:08:47 +00:00
#+end_src
Returns this string:
#+begin_example
"'/Users/howard.abrams/Notes/Sprint-2022-25.org' '/Users/howard.abrams/Notes/Sprint-2022-03.org' '/Users/howard.abrams/Notes/Sprint-2020-45.org' '/Users/howard.abrams/Notes/Sprint-2022-09.org' '/Users/howard.abrams/Notes/Sprint-2022-05.org' '/Users/howard.abrams/Notes/Sprint-2022-01.org' '/Users/howard.abrams/Notes/Sprint-2022-19.org'"
#+end_example
2022-04-09 15:58:18 +00:00
2022-07-05 23:08:47 +00:00
The =ha-search-notes= function prompts for the phrase to search, and then searches through the =org-directory= path, acquiring matching files, to feed to =grep= (and the [[help:grep ][grep function ]]) to display a list of matches that I can jump to.
2022-04-09 15:58:18 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-09 15:58:18 +00:00
(defun ha-search-notes (phrase &optional path)
"Search files in PATH for PHRASE and display in a grep mode buffer."
(interactive "sSearch notes for: ")
(let* ((command (if (equal system-type 'darwin) "ggrep" "grep"))
(regexp (string-replace " " "\\|" phrase))
2022-07-05 23:08:47 +00:00
(use-paths (or path (list org-directory org-journal-dir)))
(files (ha-search-notes--files phrase use-paths))
(cmd-line (format "%s -ni -m 1 '%s' %s" command regexp files)))
2022-07-07 16:26:55 +00:00
(grep cmd-line)))
2022-07-05 23:08:47 +00:00
#+end_src
2022-04-09 15:58:18 +00:00
2022-07-05 23:08:47 +00:00
Add a keybinding to the function:
#+begin_src emacs-lisp
(ha-leader "f n" '("find notes" . ha-search-notes))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
** Misc
*** Babel Blocks
2022-03-09 18:45:37 +00:00
I use [[https://orgmode.org/worg/org-contrib/babel/intro.html ][org-babel ]] (obviously) and don’ t need confirmation before evaluating a block:
2022-06-18 00:25:47 +00:00
#+name : ob-configuration
#+begin_src emacs-lisp :tangle no
2022-09-12 04:59:50 +00:00
(setq org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t
org-src-window-setup 'current-window)
2022-06-18 00:25:47 +00:00
#+end_src
2022-09-12 04:59:50 +00:00
2021-11-02 00:27:14 +00:00
Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off.
2022-06-18 00:25:47 +00:00
#+name : no-flycheck-in-org
#+begin_src emacs-lisp :tangle no
2021-11-06 00:06:55 +00:00
(defun disable-flycheck-in-org-src-block ()
2021-11-02 00:27:14 +00:00
(setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc)))
2021-11-06 00:06:55 +00:00
(add-hook 'org-src-mode-hook 'disable-flycheck-in-org-src-block)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-17 18:32:02 +00:00
2021-11-06 00:06:55 +00:00
And turn on ALL the languages:
2022-06-18 00:25:47 +00:00
#+name : ob-languages
#+begin_src emacs-lisp :tangle no
2021-11-06 00:06:55 +00:00
(org-babel-do-load-languages 'org-babel-load-languages
'((shell . t)
(js . t)
(emacs-lisp . t)
(clojure . t)
(python . t)
(ruby . t)
(dot . t)
(css . t)
(plantuml . t)))
2022-06-18 00:25:47 +00:00
#+end_src
2023-06-15 00:35:08 +00:00
*** Searching Literate Files
A noweb definition, e.g. =<<something-something>>= could /jump/ to the =#name= definition.
Since [[https://github.com/BurntSushi/ripgrep ][ripgrep ]] is pretty fast, I’ ll 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.
2023-07-05 16:25:01 +00:00
#+begin_src emacs-lisp :noweb no
2023-06-15 00:35:08 +00:00
(defun ha-org-noweb-block-jump (str pos)
"Go to a literate org file containing a symbol, STR.
The POS is ignored."
;; Sometimes I wrap a function name in `=' characters, and these should be removed:
(when (string-match (rx "<<" (group (one-or-more any)) " >>") str)
(setq str (match-string 1 str)))
(ignore-errors
(let* ((default-directory (projectile-project-root))
(command (format "rg --ignore-case --json '#\\+name: + %s' *.org" str))
(results (thread-last command
shell-command-to-list
second
json-parse-string))
(file (thread-last results
(gethash "data")
(gethash "path")
(gethash "text")))
(line (thread-last results
(gethash "data")
(gethash "line_number"))))
(find-file file)
(goto-line line))))
(add-to-list 'evil-goto-definition-functions 'ha-org-noweb-block-jump)
#+end_src
2022-06-16 19:15:43 +00:00
*** REST Web Services
2022-09-02 23:08:58 +00:00
Emacs has two ways to query and investigate REST-oriented web services. The [[https://github.com/zweifisch/ob-http ][ob-http ]] adds HTTP calls to standard org blocks.
2022-06-16 19:15:43 +00:00
#+begin_src emacs-lisp
(use-package ob-http
:init
(add-to-list 'org-babel-load-languages '(http . t)))
#+end_src
And let’ s see how it works:
2022-09-02 23:08:58 +00:00
#+begin_src http :pretty :results value replace :wrap src js :var user-agent="my-super-agent"
2022-06-16 19:15:43 +00:00
GET https://api.github.com/repos/zweifisch/ob-http/languages
Accept: application/json
User-Agent: ${user-agent}
#+end_src
#+results :
#+begin_src js
{
"Emacs Lisp": 15327,
"Shell": 139
}
#+end_src
Another approach is [[https://github.com/alf/ob-restclient.el ][ob-restclient ]], that may be based on the [[https://github.com/pashky/restclient.el ][restclient ]] project.
#+begin_src emacs-lisp
(use-package ob-restclient
:init
(add-to-list 'org-babel-load-languages '(restclient . t)))
#+end_src
And let’ s try this:
2022-09-02 23:08:58 +00:00
#+begin_src restclient :results value replace :wrap src js :var user-agent="my-super-agent"
2022-06-16 19:15:43 +00:00
GET https://api.github.com/repos/zweifisch/ob-http/languages
Accept: application/vnd.github.moondragon+json
User-Agent: ${user-agent}
#+end_src
#+results :
#+begin_src js
{
"Emacs Lisp": 15327,
"Shell": 139
}
#+end_src
2022-04-12 03:37:15 +00:00
*** Graphviz
2022-03-09 18:45:37 +00:00
The [[https://graphviz.org/ ][graphviz project ]] can be written in org blocks, and then rendered as an image:
2022-06-18 00:25:47 +00:00
#+name : ob-graphviz
#+begin_src emacs-lisp :tangle no
2022-03-09 18:45:37 +00:00
(add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot"))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 18:45:37 +00:00
For example:
2022-06-18 00:25:47 +00:00
#+begin_src dot :file support/ha-org-graphviz-example.png :exports file :results replace file
2022-03-09 18:45:37 +00:00
digraph G {
2022-04-01 18:35:27 +00:00
graph [bgcolor=transparent];
edge [color=white];
node[style=filled];
2022-03-10 01:14:21 +00:00
A -> B -> E;
A -> D;
A -> C;
E -> F;
E -> H
D -> F;
A -> H;
E -> G;
2022-03-09 18:45:37 +00:00
}
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-10 01:14:21 +00:00
2022-06-18 00:25:47 +00:00
#+attr_org : :width 400px
#+results :
2022-04-01 18:35:27 +00:00
[[file:support/ha-org-graphviz-example.png ]]
2022-04-12 03:37:15 +00:00
*** 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 ]].
2022-06-18 00:25:47 +00:00
#+begin_src sh
2022-04-12 03:37:15 +00:00
curl -o ~/bin/plantuml.jar https:/ /github.com/plantuml/plantuml/releases/download/v1.2022.4/plantuml-1.2022.4.jar
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 03:37:15 +00:00
After installing the [[https://github.com/skuro/plantuml-mode ][plantuml-mode ]], we need to reference the location:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-12 03:37:15 +00:00
(use-package plantuml-mode
:straight (:host github :repo "skuro/plantuml-mode")
:init
(setq org-plantuml-jar-path (expand-file-name "~/bin/plantuml.jar")))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 03:37:15 +00:00
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
- =component= :: https://plantuml.com/component-diagram
- =deployment= :: https://plantuml.com/deployment-diagram
- =object= :: https://plantuml.com/object-diagram
- =sequence= :: https://plantuml.com/sequence-diagram
- =state= :: https://plantuml.com/state-diagram
- =timing= :: https://plantuml.com/timing-diagram
- =use-case= :: https://plantuml.com/use-case-diagram
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-12 03:37:15 +00:00
(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)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-12 03:37:15 +00:00
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
@startuml
!include https://raw.githubusercontent.com/ptrkcsk/one-dark-plantuml-theme/v1.0.0/theme.puml
' See details at https://plantuml.com/sequence-diagram
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: Another authentication Response
@enduml
#+end_src
2022-06-18 00:25:47 +00:00
#+attr_org : :width 800px
2022-04-12 03:37:15 +00:00
[[file:ha-org-plantuml-example.png ]]
2021-11-02 00:27:14 +00:00
*** 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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-12 05:05:41 +00:00
(defun ha-org-next-image-number (&optional prefix)
2021-11-02 00:27:14 +00:00
(when (null prefix)
(if (null (buffer-file-name))
(setq prefix "cool-image")
(setq prefix (file-name-base (buffer-file-name)))))
(save-excursion
(goto-char (point-min))
(let ((largest 0)
(png-reg (rx (literal prefix) "-" (group (one-or-more digit)) (or ".png" ".svg"))))
(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)))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-06 00:07:33 +00:00
** Keybindings
2022-03-09 06:01:19 +00:00
Global keybindings available to all file buffers:
2022-06-18 00:25:47 +00:00
#+name : global-keybindings
#+begin_src emacs-lisp :tangle no
2022-01-06 23:36:39 +00:00
(ha-leader
2023-06-15 22:44:57 +00:00
"o l" '("store link" . org-store-link)
2022-01-06 23:36:39 +00:00
"o x" '("org capture" . org-capture)
2023-10-12 22:51:12 +00:00
"o C" '("clock out" . org-clock-out))
(ha-leader :keymaps 'org-mode-map
"o e" '("exports" . org-export-dispatch)
"o L" '("insert link" . org-insert-link)
"o P" '("set property" . org-set-property)
"o g" '("set tags" . org-set-tags-command)
"o t" '("todo" . org-todo)
"o T" '("list todos" . org-todo-list)
"o i" '(:ignore t :which-key "insert")
"o i i" '("item" . org-insert-item)
"o i I" '("insert id" . org-id-get-create)
"o i l" '("link" . org-insert-link)
"o i d" '("drawer" . org-insert-drawer)
"o i h" '("heading" . org-insert-heading)
"o i s" '("subheading" . org-insert-subheading)
"o o" '(:ignore t :which-key "toggles")
"o o h" '("heading" . org-toggle-heading)
"o o i" '("item" . org-toggle-item)
"o o x" '("checkbox" . org-toggle-checkbox)
"o o I" '("images" . org-toggle-inline-images)
"o o m" '("markup" . (lambda () (interactive)
(setq org-hide-emphasis-markers (not org-hide-emphasis-markers)) (font-lock-update)))
"o /" '("agenda" . consult-org-agenda)
"o '" '("edit" . org-edit-special)
"o *" '("C-c *" . org-ctrl-c-star)
"o +" '("C-c -" . org-ctrl-c-minus)
"o c" '(:ignore t :which-key "clocks")
"o c i" '("clock in" . org-clock-in)
"o c l" '("clock in last" . org-clock-in-last)
"o c o" '("clock out" . org-clock-out)
"o c c" '("cancel" . org-clock-cancel)
"o c d" '("mark default task" . org-clock-mark-default-task)
"o c e" '("modify effort" . org-clock-modify-effort-estimate)
"o c E" '("set effort" . org-set-effort)
"o c g" '("goto clock" . org-clock-goto)
"o c r" '("resolve clocks" . org-resolve-clocks)
"o c R" '("clock report" . org-clock-report)
"o c t" '("eval range" . org-evaluate-time-range)
"o c =" '("timestamp up" . org-clock-timestamps-up)
"o c -" '("timestamp down" . org-clock-timestamps-down)
"o d" '(:ignore t :which-key "dates")
"o d s" '("schedule" . org-schedule)
"o d d" '("deadline" . org-deadline)
"o d t" '("timestamp" . org-time-stamp)
"o d T" '("inactive time" . org-time-stamp-inactive)
"o b" '(:ignore t :which-key "tables")
"o b -" '("insert hline" . org-table-insert-hline)
"o b a" '("align" . org-table-align)
"o b b" '("blank field" . org-table-blank-field)
"o b c" '("create teable" . org-table-create-or-convert-from-region)
"o b e" '("edit field" . org-table-edit-field)
"o b f" '("edit formula" . org-table-edit-formulas)
"o b h" '("field info" . org-table-field-info)
"o b s" '("sort lines" . org-table-sort-lines)
"o b r" '("recalculate" . org-table-recalculate)
"o b d" '(:ignore t :which-key "delete")
"o b d c" '("delete column" . org-table-delete-column)
"o b d r" '("delete row" . org-table-kill-row)
"o b i" '(:ignore t :which-key "insert")
"o b i c" '("insert column" . org-table-insert-column)
"o b i h" '("insert hline" . org-table-insert-hline)
"o b i r" '("insert row" . org-table-insert-row)
"o b i H" '("insert hline ↓" . org-table-hline-and-move)
"o n" '(:ignore t :which-key "narrow")
"o n s" '("subtree" . org-narrow-to-subtree)
"o n b" '("block" . org-narrow-to-block)
"o n e" '("element" . org-narrow-to-element)
"o n w" '("widen" . widen))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-17 18:32:02 +00:00
2021-11-09 16:19:16 +00:00
Bindings specific to org files:
2022-06-18 00:25:47 +00:00
#+name : org-keybindings
#+begin_src emacs-lisp :tangle no
2022-11-04 05:16:16 +00:00
(evil-define-key '(normal motion operator visual)
org-mode-map
2023-06-15 22:44:57 +00:00
"gj" '("next heading" . #'org-forward-heading-same-level)
"gk" '("prev heading" . #'org-backward-heading-same-level)
"gb" '("next block" . #'org-next-block)
"gB" '("prev block" . #'org-previous-block))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Supporting Packages
** Exporters
2022-03-09 06:01:19 +00:00
Limit the number of exporters to the ones that I would use:
2022-06-18 00:25:47 +00:00
#+name : ox-exporters
#+begin_src emacs-lisp
2021-11-24 00:40:50 +00:00
(setq org-export-backends '(ascii html icalendar md odt))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-24 00:40:50 +00:00
I have a special version of tweaked [[file:elisp/ox-confluence.el ][Confluence exporter ]] for my org files:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-24 00:40:50 +00:00
(use-package ox-confluence
:after org
2021-12-27 17:27:25 +00:00
:straight nil ; Located in my "elisp" directory
2021-11-24 00:40:50 +00:00
:config
2023-10-12 22:51:12 +00:00
(ha-leader :keymaps 'org-mode-map
"o E" '("to confluence" . ox-export-to-confluence)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode ][graphviz-dot-mode ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(use-package graphviz-dot-mode
:mode "\\.dot\\'"
:init
(setq tab-width 4
graphviz-dot-indent-width 2
graphviz-dot-auto-indent-on-newline t
graphviz-dot-auto-indent-on-braces t
graphviz-dot-auto-indent-on-semi t))
2022-06-18 00:25:47 +00:00
#+end_src
2023-03-13 17:54:51 +00:00
*** HTML Style
Splitting out HTML snippets is often a way that I can transfer org-formatted content to other applications.
#+name : html-exporting
#+begin_src emacs-lisp
(setq org-html-head-extra
(string-join '( "<style >"
2023-03-24 17:58:22 +00:00
"@import url('https://fonts.googleapis.com/css2?family=Literata:ital,wght@0,300;0,600;1,300;1,600&display=swap');"
2023-03-13 17:54:51 +00:00
"@import url('https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,600;1,300;1,600&display=swap');"
2023-04-03 16:29:08 +00:00
"body { font-family: 'Literata', sans-serif; color: #333; }"
2023-03-24 17:58:22 +00:00
"h1,h2,h3,h4,h5 { font-family: 'Overpass', sans-serif; color: #333; }"
2023-04-19 15:47:47 +00:00
"code { color: steelblue }"
"pre { background-color: #eee; border-color: #aaa; }"
"a { text-decoration-style: dotted }"
2023-04-03 16:29:08 +00:00
"@media (prefers-color-scheme: dark) {"
2023-04-19 15:47:47 +00:00
" body { background-color: #1d1f21; color: white; }"
" h1,h2,h3,h4,h5 { color: #fcca1b; }"
" code { color: lightsteelblue; }"
" pre { background-color: black; border-color: #777; }"
" a:link { color: lightblue }"
" a:visited { color: violet }"
2023-04-03 16:29:08 +00:00
"}"
2023-03-13 17:54:51 +00:00
"</style >")
hard-newline))
#+end_src
2022-05-31 18:59:30 +00:00
** 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 ]],
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-31 18:59:30 +00:00
(use-package async)
(use-package ha-focus
:straight (:type built-in)
:config
(ha-leader
"o f" '("begin focus" . ha-focus-begin)
"o F" '("break focus" . ha-focus-break)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:09:43 +00:00
** Spell Checking
2022-05-31 18:59:30 +00:00
Let's hook some spell-checking into org files, and actually all text files. I’ m 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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-31 18:59:30 +00:00
(setq-default abbrev-mode t)
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-31 18:59:30 +00:00
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~ ):
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-31 18:59:30 +00:00
(ha-leader "x d" '("add abbrev" . kadd-global-abbrev))
2022-06-18 00:25:47 +00:00
#+end_src
2022-05-31 18:59:30 +00:00
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 Evil’ s [[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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-05-31 18:59:30 +00:00
(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
other mistakes are automatically corrected."
(interactive "p")
(save-excursion
(evil-prev-flyspell-error count)
(when (looking-at (rx (one-or-more (any alnum "-" "_"))))
(let ((start-word (match-beginning 0))
(bad-word (match-string 0)))
(ispell-word)
(define-global-abbrev bad-word (buffer-substring-no-properties start-word (point)))))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:09:43 +00:00
2022-05-31 18:59:30 +00:00
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 don’ t 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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 19:09:43 +00:00
(use-package flyspell
:hook (text-mode . flyspell-mode)
2022-05-31 18:59:30 +00:00
:bind (("M-S" . ha-fix-last-spelling) ; This is j-k-s on the Moonlander. Hrm.
("s-s" . ha-fix-last-spelling)) ; This is Command-s or ;-s on Moonlander
2022-05-16 20:34:05 +00:00
:general
(:states 'insert :keymaps 'text-mode-map
"M-s M-s" 'ha-fix-last-spelling)
2022-02-17 17:50:34 +00:00
:init
;; Tell ispell.el that ’ can be part of a word.
(setq ispell-local-dictionary-alist
`((nil "[[:alpha: ]]" "[^[:alpha:]]"
"['\x2019]" nil ("-B") nil utf-8)))
2021-12-29 19:09:43 +00:00
:config
2023-10-12 22:51:12 +00:00
(ha-leader :keymaps 'text-mode-map
"S" '(:ignore t :which-key "spellcheck")
"S s" '("correct last misspell" . ha-fix-last-spelling)
"S b" '("check buffer" . flyspell-buffer)
"S c" '("correct word" . flyspell-auto-correct-word)
"S p" '("previous misspell" . evil-prev-flyspell-error)
"S n" '("next misspell" . evil-next-flyspell-error))
2022-10-22 05:08:02 +00:00
;; Let's use M-TAB for something else ...
(define-key flyspell-mode-map (kbd "M-TAB") nil))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:09:43 +00:00
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.
2021-12-29 19:43:41 +00:00
2022-02-17 17:50:34 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-02-17 17:50:34 +00:00
(defun endless/replace-apostrophe (args)
"Don't send ’ to the subprocess."
(cons (replace-regexp-in-string
"’ " "'" (car args))
(cdr args)))
(advice-add #'ispell-send-string :filter-args #'endless/replace-apostrophe)
(defun endless/replace-quote (args)
"Convert ' back to ’ from the subprocess."
(if (not (derived-mode-p 'org-mode))
args
(cons (replace-regexp-in-string
"'" "’ " (car args))
(cdr args))))
(advice-add #'ispell-parse-output :filter-args #'endless/replace-quote)
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-17 17:50:34 +00:00
The end result? No misspellings. Isn‘ t this nice?
2021-12-29 19:43:41 +00:00
Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoValeriy/emacs-powerthesaurus ][powerthesaurus ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 19:43:41 +00:00
(use-package powerthesaurus
2022-09-16 22:38:59 +00:00
:bind ("s-t" . powerthesaurus-lookup-dwim)
2021-12-29 19:43:41 +00:00
:config
2023-10-12 22:51:12 +00:00
(ha-leader :keymaps 'text-mode-map
"S t" '("thesaurus" . powerthesaurus-lookup-dwim)
"S s" '("synonyms" . powerthesaurus-lookup-synonyms-dwim)
"S a" '("antonyms" . powerthesaurus-lookup-antonyms-dwim)
"S r" '("related" . powerthesaurus-lookup-related-dwim)
"S S" '("sentence" . powerthesaurus-lookup-sentences-dwim)))
2022-06-18 00:25:47 +00:00
#+end_src
2021-12-29 19:43:41 +00:00
2022-09-16 22:38:59 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-29 19:43:41 +00:00
(use-package define-word
:config
2023-10-12 22:51:12 +00:00
(ha-leader :keymaps 'text-mode-map
"S d" '("define this" . define-word-at-point)
"S D" '("define word" . define-word)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-09-16 22:38:59 +00:00
After my enamoring of Noah Webster’ s 1913 dictionary (originally due to reading [[https://janusworx.com/blog/thank-god-for-noah/ ][this essay ]] by Mario Jason Braganza who referred to James Somers’ original [[https://jsomers.net/blog/dictionary ][2014 blog entry ]]), I easily followed the instructions from [[https://github.com/ponychicken/WebsterParser ][WebsterParser ]], a Github project, with the dictionary:
1. Download [[https://github.com/ponychicken/WebsterParser/releases/latest/download/websters-1913.dictionary.zip ][the dictionary ]] file.
2. Unzip the archive … have a *Finder* window open to the =.dictionary= file.
3. Open the =Dictionary.app= program.
4. Select the menu entry, *Dictionary – > File – > Open Dictionaries Folder*
5. Drag the downloaded =Websters-1913.dictionary= file into the folder
6. Select the menu entry, *Dictionary – > Dictionary – > Preferences*
7. Check the now last dictionary in the list
If you want to always see Webster’ s results by default, go to the Dictionary app’ s preferences and drag Webster’ s to the top of the list.
2022-09-27 00:22:20 +00:00
Now that I’ m mostly on version 28 and above of Emacs, we can take advantage of [[help:dictionary-search ][dictionary-search ]] for looking up dictionaries online, and out of all the word definitions packages for Emacs, this looks the best and is easiest to read:
2022-09-16 22:38:59 +00:00
#+begin_src emacs-lisp
2022-09-27 00:22:20 +00:00
(setq dictionary-server "dict.org")
2023-10-12 22:51:12 +00:00
(ha-leader :keymaps 'text-mode-map
"S d" '("define word" . dictionary-search))
2022-09-16 22:38:59 +00:00
#+end_src
2022-09-27 00:22:20 +00:00
Once in the dictionary buffer, acquiesce these keybindings:
- ~q~ close the dictionary buffer
- ~s~ ask for a new word to search
- ~d~ search the word at point
Also note that the dictionary has links to other pages, so ~n~ and ~TAB~ jumps to the next link and ~RET~ opens that link.
2022-03-09 06:01:19 +00:00
** 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 isn’ t integrated into =flycheck= :
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :tangle no
2022-03-09 06:01:19 +00:00
(use-package writegood-mode
2022-09-21 06:11:29 +00:00
:hook (org-mode . writegood-mode))
2022-06-18 00:25:47 +00:00
#+end_src
And it reports obnoxious messages.
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
We install the =write-good= NPM:
2022-06-18 00:25:47 +00:00
#+begin_src shell
2022-03-09 06:01:19 +00:00
npm install -g write-good
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
And check that the following works:
2022-06-18 00:25:47 +00:00
#+begin_src sh :results output
2022-03-09 06:01:19 +00:00
write-good --text="So it is what it is."
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
Now, let’ s connect it to flycheck:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-15 16:54:30 +00:00
(use-package flycheck
:config
(flycheck-define-checker write-good
"A checker for prose"
:command ("write-good" "--parse" source-inplace)
:standard-input nil
:error-patterns
((warning line-start (file-name) ":" line ":" column ":" (message) line-end))
:modes (markdown-mode org-mode text-mode))
2022-06-16 18:17:54 +00:00
(add-to-list 'flycheck-checkers 'write-good))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
*** 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/ .
2022-06-18 00:25:47 +00:00
#+begin_src sh
2022-03-09 06:01:19 +00:00
brew install proselint
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
Next, create a configuration file, =~/.config/proselint/config= file, to turn on/off checks:
2022-06-18 00:25:47 +00:00
#+begin_src js :tangle ~/.config/proselint/config.json :mkdirp yes
2022-03-25 18:11:15 +00:00
{
"checks": {
"typography.diacritical_marks": false,
"annotations.misc": false,
"consistency.spacing": false
}
2022-03-09 06:01:19 +00:00
}
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:19 +00:00
And tell [[https://www.flycheck.org/ ][flycheck ]] to use this:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-09 06:01:19 +00:00
(use-package flycheck
2022-06-16 18:17:54 +00:00
:config
(add-to-list 'flycheck-checkers 'proselint)
;; And create the chain of checkers so that both work:
(flycheck-add-next-checker 'write-good 'proselint))
#+end_src
*** Textlint
The [[https://textlint.github.io/ ][textlint ]] project comes with =flycheck= , as long as there is an executable:
#+begin_src sh
npm install -g textlint
# And all the rules
npm install -g textlint-rule-alex
npm install -g textlint-rule-diacritics
npm install -g textlint-rule-en-max-word-count
npm install -g textlint-rule-max-comma
npm install -g textlint-rule-no-start-duplicated-conjunction
npm install -g textlint-rule-period-in-list-item
npm install -g textlint-rule-stop-words
npm install -g textlint-rule-terminology
npm install -g textlint-rule-unexpanded-acronym
#+end_src
I create a configuration file in my home directory:
#+begin_src js :tangle ~/.textlintrc
{
"filters": {},
"rules": {
"abbr-within-parentheses": false,
"alex": true,
"common-misspellings": false,
"diacritics": true,
"en-max-word-count": true,
"max-comma": true,
"no-start-duplicated-conjunction": true,
"period-in-list-item": true,
"stop-words": true,
"terminology": true,
"unexpanded-acronym": true,
"write-good": false
}
}
#+end_src
Add =textlint= to the /chain/ for Org files:
#+begin_src emacs-lisp
(use-package flycheck
:config
(setq flycheck-textlint-config (format "%s/.textlintrc" (getenv "HOME")))
(flycheck-add-next-checker 'proselint 'textlint))
2022-06-18 00:25:47 +00:00
#+end_src
2022-09-09 23:40:53 +00:00
** Perfect Sentence
Chris Malorana’ s [[https://www.youtube.com/watch?v=E-yk_V5TnNU ][video tutorial ]] demonstrates the ability to extrude a single sentence into another buffer, edit different versions of that sentence, and replace one version into the original buffer. Similar to how org-mode edits blocks.
The idea is based on Jordan Peterson's writing app, Essay. Love the idea, and thought I might work on it. The difference is that I want my version more resilient and not as dependent on the context.
When we create a new buffer, we want a number of /buffer-local/ variables, so that we know where to return:
#+begin_src emacs-lisp
(defvar-local ha-sentence-buffer nil
"The name of the buffer to return when completed.")
(defvar-local ha-sentence-begin nil
"The beginning position in the original buffer to replace text.")
(defvar-local ha-sentence-end nil
"The ending position in the original buffer to replace text.")
#+end_src
My first thought is how to select the sentence. Sure, sometimes that should be the /region/ , but we can also use the help:bounds-of-thing-at-point to define the start and the end of the current sentence:
#+begin_src emacs-lisp
(defun ha-sentence--select-region (type-of-thing &optional start end)
"Return a tuple of the start and end of the selected sentence."
(cond
((region-active-p) (cons (region-beginning) (region-end)))
((and start end) (cons start end))
(t (bounds-of-thing-at-point type-of-thing))))
#+end_src
In the original buffer, we want to edit a /sentence/ , but in the editing buffer, a single sentence may expand to multiple sentences, so we need to change whether we select a ='sentence= or a ='defun= (for a paragraph).
With this function, we can call [[help:cl-destructuring-bind ][destructuring-bind ]] to define what section we want to edit by assigning the =start= and =end= values. Now we create another buffer window, set the local variables, and insert the region/sentence we requested:
#+begin_src emacs-lisp
(defun ha-sentence-break (&optional start end)
"Break a sentence out and work it in a new buffer.
A sentence chosen is based on the location of a point,
or the active region."
(interactive)
(cl-destructuring-bind (start . end) (ha-sentence--select-region 'sentence start end)
(let ((orig-mode major-mode)
(orig-buffer (current-buffer))
(orig-sentence (buffer-substring-no-properties start end)))
(switch-to-buffer-other-window "**sentence-breakout* *")
(funcall orig-mode)
(ha-sentence-buffer-mode)
;; Store some breadcrumbs so we can return where we left off:
(setq-local ha-sentence-buffer orig-buffer
ha-sentence-begin start
ha-sentence-end end)
(erase-buffer)
(insert orig-sentence)
;; Because we might want to duplicate the sentence in the
;; buffer, let's put it on the kill ring:
(kill-new orig-sentence))))
#+end_src
With the new buffer displayed, the sentence to edit is shown, and the idea is to write different versions of that sentence. When we have the version we like, we hit ~C-c C-c~ which calls [[help:ha-sentence-choose ][ha-sentence-choose ]] /to choose/ the version that replaces the old one. But what if a sentence becomes multiple sentences? Well, in that case, we need to select the text before hitting the ~C-c C-c~ sequence. The buffer-local variables tell us which buffer to return, and what text to replace.
#+begin_src emacs-lisp
(defun ha-sentence-choose (&optional start end)
"Choose a sentence and go back to the other window."
(interactive)
;; By default, our "region" is a paragraph using 'defun symbol of `thing-at-point'
;; It doesn't work on the last sentence if it doesn't include a
;; newline, so hackily, we insert one.
(save-excursion
(goto-char (point-max))
(insert "\n"))
(cl-destructuring-bind (start . end) (ha-sentence--select-region 'defun start end)
(let ((chosen-sentence (buffer-substring-no-properties start end))
(orig-buffer ha-sentence-buffer)
(orig-start ha-sentence-begin)
(orig-end ha-sentence-end))
(kill-buffer-and-window)
(switch-to-buffer orig-buffer)
(delete-region orig-start orig-end) ; Or call `kill-region' to put on clipboard?
(insert chosen-sentence))))
#+end_src
The [[help:kill-region ][kill-region ]] function takes the original text and places it on the [[help:kill-ring ][kill-ring ]] (the clipboard). But since we already copied that when we created the buffer, we call [[help:delete-region ][delete-region ]] instead. Especially since if we felt like we made a mistake, we could just undo the changes.
With my limited experience, I seldom enter completely difference sentences. Instead, I want to /copy/ the sentence and work on that. Let’ s make a function to duplicate it.
#+begin_src emacs-lisp
(defun ha-sentence-duplicate ()
(interactive)
(let ((current (thing-at-point 'defun)))
(goto-char (point-max))
(insert "\n\n")
(let ((starting-point (point)))
(insert current)
(goto-char starting-point))))
#+end_src
When creating this new editing buffer, we need keybindings that exist only for this buffer, in other words, a [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html ][minor mode ]]:
#+begin_src emacs-lisp
(defvar ha-sentence-buffer-mode-map (make-sparse-keymap) "Keymap for `my-mode'.")
(define-key ha-sentence-buffer-mode-map (kbd "C-c C-c") #'ha-sentence-choose)
(define-key ha-sentence-buffer-mode-map (kbd "C-c C-k") #'kill-buffer-and-window)
(define-key ha-sentence-buffer-mode-map (kbd "C-c C-d") #'ha-sentence-duplicate)
(define-minor-mode ha-sentence-buffer-mode
"Toggle the Perfect Sentence mode.
Interactively with no argument, this command toggles the mode.
A positive prefix argument enables the mode, any other prefix
argument disables it. From Lisp, argument omitted or nil enables
the mode, `toggle' toggles the state.
When this mode is enabled, `C-c C-c' calls `ha-sentence-choose',
and `C-c C-k' cancels and buries the buffer."
;; :interactive nil
:init-value nil
:lighter " PS"
:keymap ha-sentence-buffer-mode-map)
#+end_src
Let’ s bind a couple key sequences for Emacs mode:
#+begin_src emacs-lisp
(global-set-key (kbd "M-s b") 'ha-sentence-break)
#+end_src
I am making this global, as it may be nice in both org-mode and programming modes.
And something else while in Evil mode:
#+begin_src emacs-lisp
(ha-leader "x b" '("edit sentence" . ha-sentence-break))
#+end_src
Perhaps he might get around to turning [[https://git.chrismaiorana.com/?p=sentinel.git;a=blob;f=sentin.el;h=2738eff6ac2b0877576bafe88878683a7eff3125;hb=refs/heads/master ][his code ]] into a package. Features needed include:
- Adding an overlay to the original text, ala help:org-src--make-source-overlay
2022-03-25 18:02:02 +00:00
** Distraction-Free Writing
2022-06-16 18:17:54 +00:00
[[https://christopherfin.com/writing/emacs-writing.html ][Christopher Fin's essay ]] inspired me to clean my writing room.
2022-03-25 18:02:02 +00:00
*** Write-room
2022-03-09 06:01:19 +00:00
For a complete focused, /distraction-free/ environment, for writing or concentrating, I'm using [[https://github.com/joostkremers/writeroom-mode ][Writeroom-mode ]]:
2021-12-30 02:52:00 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-12-30 02:52:00 +00:00
(use-package writeroom-mode
:hook (writeroom-mode-disable . winner-undo)
2022-06-18 00:25:47 +00:00
:init
2021-12-30 02:52:00 +00:00
(ha-leader "t W" '("writeroom" . writeroom-mode))
(ha-leader :keymaps 'writeroom-mode-map
"=" '("adjust width" . writeroom-adjust-width)
"<" '("decrease width" . writeroom-decrease-width)
">" '("increase width" . writeroom-increase-width))
:bind (:map writeroom-mode-map
("C-M-<" . writeroom-decrease-width)
("C-M->" . writeroom-increase-width)
("C-M-=" . writeroom-adjust-width)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
*** Olivetti
2022-06-16 18:17:54 +00:00
The [[https://github.com/rnkn/olivetti ][olivetti project ]] sets wide margins and centers the text. It isn’ t better than Writeroom, but, it works well with Logos (below).
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 18:02:02 +00:00
(use-package olivetti
:init
(setq-default olivetti-body-width 100)
(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)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-25 18:02:02 +00:00
*** 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 ]]:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-25 18:02:02 +00:00
(use-package logos
2022-08-09 16:57:20 +00:00
:straight (:host gitlab :repo "protesilaos/logos")
2022-03-25 18:02:02 +00:00
:init
(setq logos-outlines-are-pages t
logos-outline-regexp-alist
`((emacs-lisp-mode . "^;;;+ ")
(org-mode . "^\\*+ + ")
(t . ,(or outline-regexp logos--page-delimiter))))
2022-06-16 18:17:54 +00:00
;; These apply when enabling `logos-focus-mode' as buffer-local.
2022-03-25 18:02:02 +00:00
(setq-default logos-hide-mode-line t
logos-scroll-lock nil
logos-indicate-buffer-boundaries nil
logos-buffer-read-only nil
logos-olivetti t)
:config
(ha-leader "t L" '("logos" . logos-focus-mode))
2022-05-16 20:34:05 +00:00
(define-key global-map [remap narrow-to-region] #'logos-narrow-dwim)
:general
(:states 'normal
2023-06-15 22:44:57 +00:00
"g [" '("back page" . logos-backward-page-dwim)
"g ]" '("next page" . logos-forward-page-dwim)))
2022-06-18 00:25:47 +00:00
#+end_src
2023-03-13 17:54:51 +00:00
* Technical Artifacts :noexport:
2022-03-09 06:01:19 +00:00
Let's provide a name, to allow =require= to work:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2021-11-02 00:27:14 +00:00
(provide 'ha-org)
;;; ha-org.el ends here
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
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 configuring org-mode and those files.
#+PROPERTY : header-args:sh :tangle no
2023-07-05 20:20:55 +00:00
#+PROPERTY : header-args:emacs-lisp :tangle yes :noweb yes
2021-11-02 00:27:14 +00:00
#+PROPERTY : header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS : num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS : skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT : view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js