2021-11-02 00:27:14 +00:00
#+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
2022-03-09 18:45:37 +00:00
;;; ha --- Org configuration. -*- lexical-binding: t; -* -
;;
;; © 2020-2022 Howard X. Abrams
;; This work is 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: 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
2021-11-17 18:32:02 +00:00
#+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
(use-package org
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 >>
2022-03-09 18:45:37 +00:00
<<ob-configuration >>
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 >>)
2021-11-02 00:27:14 +00:00
#+END_SRC
2021-11-24 00:34:48 +00:00
One other helper routine is a =general= macro for org-mode files:
#+NAME : ha-org-leader
#+BEGIN_SRC emacs-lisp :tangle no
(general-create-definer ha-org-leader
2021-12-08 21:57:42 +00:00
:states '(normal visual motion)
:keymaps 'org-mode-map
2021-12-14 19:26:11 +00:00
:prefix "SPC m"
:global-prefix "<f17 >"
2021-12-14 19:42:26 +00:00
:non-normal-prefix "S-SPC")
2021-11-24 00:34:48 +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:
2021-11-17 18:32:02 +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
2022-02-02 19:48:19 +00:00
org-export-with-sub-superscripts 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)
2021-11-02 00:27:14 +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:
2021-11-17 18:32:02 +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)
#+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:
2021-11-17 18:32:02 +00:00
#+NAME : text-files
#+BEGIN_SRC emacs-lisp :tangle no
(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))
2021-11-02 00:27:14 +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
#+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)))
#+END_SRC
And bind it to the Return key:
2021-11-17 18:32:02 +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)
2021-11-02 00:27:14 +00:00
#+END_SRC
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
#+BEGIN_SRC emacs-lisp
2021-11-12 05:05:41 +00:00
(defun ha-org-special-return (&optional ignore)
2021-11-06 00:07:33 +00:00
"Add new list item, heading or table row with RET.
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))))
;; ((org-at-heading-p)
;; (if (string= "" (org-element-property :title (org-element-context)))
;; (delete-region (line-beginning-position) (line-end-position))
;; (org-insert-heading-after-current)))
((org-at-table-p)
(if (-any?
(lambda (x) (not (string= "" x)))
(nth
(- (org-table-current-dline) 1)
(org-table-to-lisp)))
(org-return)
;; empty row
(beginning-of-line)
(setf (buffer-substring
(line-beginning-position) (line-end-position)) "")
(org-return)))
(t
(org-return)))))
2021-11-02 00:27:14 +00:00
#+END_SRC
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.
2021-11-02 00:27:14 +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))))
#+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:
2021-11-02 00:27:14 +00:00
#+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
** Tasks
I need to add a /blocked/ state:
2021-11-17 18:32:02 +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)")))
#+END_SRC
And I would like to have cute little icons for those states:
2021-11-17 18:32:02 +00:00
#+NAME : org-font-lock
2021-11-02 00:27:14 +00:00
#+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) "•")))))))
2021-12-29 19:09:43 +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
#+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."
(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."))
#+END_SRC
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
#+BEGIN_SRC emacs-lisp
(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
#+END_SRC
2022-04-09 15:58:18 +00:00
** 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= . Let’ s create operating-system functions the command line for searching:
#+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)."
(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))
#+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
(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"
(let ((command (if (equal system-type 'darwin)
(ha-search-notes--macos phrase path)
(ha-search-notes--linux phrase path))))
(->> command
(shell-command-to-list) ; Function from piper!
(--remove (s-matches? "~$" it))
(--remove (s-matches? "#" it))
(--map (format "'%s'" it))
(s-join " "))))
#+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
(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))
(use-paths (if path path (list org-directory org-journal-dir)))
(files (ha-search-notes--files phrase use-paths)))
(grep (format "%s -ni -m 1 '%s' %s" command regexp files))))
#+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
(ha-leader "f n" '("find notes" . ha-search-notes))
#+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:
#+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
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.
2021-11-17 18:32:02 +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)
#+END_SRC
2021-11-17 18:32:02 +00:00
2021-11-06 00:06:55 +00:00
And turn on ALL the languages:
2021-11-17 18:32:02 +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)))
2021-11-02 00:27:14 +00:00
#+END_SRC
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:
#+NAME : ob-graphviz
#+BEGIN_SRC emacs-lisp :tangle no
(add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot"))
#+END_SRC
For example:
2022-04-01 18:35:27 +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
}
#+END_SRC
2022-03-10 01:14:21 +00:00
2022-03-09 18:45:37 +00:00
#+ATTR_ORG : :width 400px
2022-03-10 01:14:21 +00:00
#+RESULTS :
2022-04-01 18:35:27 +00:00
[[file:support/ha-org-graphviz-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:
#+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)))))
#+END_SRC
*** In a PlantUML Block
To make the snippets more context aware, this predicate
#+BEGIN_SRC emacs-lisp
2021-11-12 05:05:41 +00:00
(defun ha-org-nested-in-plantuml-block ()
2021-11-02 00:27:14 +00:00
"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
2021-11-06 00:07:33 +00:00
** Keybindings
2022-03-09 06:01:19 +00:00
Global keybindings available to all file buffers:
2021-11-17 18:32:02 +00:00
#+NAME : global-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
2022-01-06 23:36:39 +00:00
(ha-leader
"o l" '("store link" . org-store-link)
"o x" '("org capture" . org-capture)
"o c" '("clock out" . org-clock-out))
2021-11-10 01:28:58 +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:
2021-11-17 18:32:02 +00:00
#+NAME : org-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
2021-11-24 00:34:48 +00:00
(ha-org-leader
2021-11-15 04:42:15 +00:00
"e" '("exports" . org-export-dispatch)
2022-01-05 00:13:44 +00:00
"I" '("insert id" . org-id-get-create)
2021-11-10 01:27:54 +00:00
"l" '("insert link" . org-insert-link)
2022-01-06 23:36:39 +00:00
"N" '("store link" . org-store-link)
2021-11-15 04:42:15 +00:00
"o" '("goto link" . ace-link-org)
2022-01-06 23:36:39 +00:00
"P" '("set property" . org-set-property)
2022-01-05 00:13:44 +00:00
"q" '("set tags" . org-set-tags-command)
"t" '("todo" . org-todo)
"T" '("list todos" . org-todo-list)
"h" '("toggle heading" . org-toggle-heading)
"i" '("toggle item" . org-toggle-item)
"x" '("toggle checkbox" . org-toggle-checkbox)
"." '("goto heading" . consult-org-heading)
"/" '("agenda" . consult-org-agenda)
"'" '("edit" . org-edit-special)
"*" '("C-c *" . org-ctrl-c-star)
"+" '("C-c -" . org-ctrl-c-minus)
"d" '(:ignore t :which-key "dates")
"d s" '("schedule" . org-schedule)
"d d" '("deadline" . org-deadline)
"d t" '("timestamp" . org-time-stamp)
"d T" '("inactive time" . org-time-stamp-inactive)
"b" '(:ignore t :which-key "tables")
"b -" '("insert hline" . org-table-insert-hline)
"b a" '("align" . org-table-align)
"b b" '("blank field" . org-table-blank-field)
"b c" '("create teable" . org-table-create-or-convert-from-region)
"b e" '("edit field" . org-table-edit-field)
"b f" '("edit formula" . org-table-edit-formulas)
"b h" '("field info" . org-table-field-info)
"b s" '("sort lines" . org-table-sort-lines)
"b r" '("recalculate" . org-table-recalculate)
"b d" '(:ignore t :which-key "delete")
"b d c" '("delete column" . org-table-delete-column)
"b d r" '("delete row" . org-table-kill-row)
"b i" '(:ignore t :which-key "insert")
"b i c" '("insert column" . org-table-insert-column)
"b i h" '("insert hline" . org-table-insert-hline)
"b i r" '("insert row" . org-table-insert-row)
"b i H" '("insert hline ↓" . org-table-hline-and-move)
2021-11-09 16:19:16 +00:00
"n" '(:ignore t :which-key "narrow")
"n s" '("subtree" . org-narrow-to-subtree)
2021-11-15 04:42:15 +00:00
"n b" '("block" . org-narrow-to-block)
2021-11-09 16:19:16 +00:00
"n e" '("element" . org-narrow-to-element)
2021-11-15 04:42:15 +00:00
"n w" '("widen" . widen))
2021-11-06 00:07:33 +00:00
#+END_SRC
2021-11-17 18:32:02 +00:00
2022-03-09 06:01:19 +00:00
Oh, and we'll use [[https://github.com/abo-abo/ace-link ][ace-link ]] for jumping:
2021-11-18 17:40:12 +00:00
#+BEGIN_SRC emacs-lisp
(use-package ace-link
:after org
:config
(define-key org-mode-map (kbd "s-o") 'ace-link-org))
2021-11-10 01:27:54 +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:
2021-11-24 00:40:50 +00:00
#+NAME : ox-exporters
#+BEGIN_SRC emacs-lisp
(setq org-export-backends '(ascii html icalendar md odt))
#+END_SRC
I have a special version of tweaked [[file:elisp/ox-confluence.el ][Confluence exporter ]] for my org files:
#+BEGIN_SRC emacs-lisp
(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
(ha-org-leader
"E" '("to confluence" . ox-export-to-confluence)))
2021-11-02 00:27:14 +00:00
#+END_SRC
And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode ][graphviz-dot-mode ]]:
#+BEGIN_SRC emacs-lisp
(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))
#+END_SRC
And we can install company support:
#+BEGIN_SRC emacs-lisp :tangle no
(use-package company-graphviz-dot)
#+END_SRC
2021-12-29 19:09:43 +00:00
** Spell Checking
2022-03-09 06:01:19 +00:00
Let's hook some spell-checking into org files, and actually all text files. We'll use [[https://www.emacswiki.org/emacs/FlySpell ][flyspell ]] mode to highlight the misspelled words, and use the venerable [[https://www.emacswiki.org/emacs/InteractiveSpell ][ispell ]] for correcting.
2021-12-29 19:09:43 +00:00
#+BEGIN_SRC emacs-lisp
(use-package flyspell
:hook (text-mode . flyspell-mode)
:bind ("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
(defun ha-fix-last-spelling (count)
"Jump to the last misspelled word, and correct it."
(interactive "p")
(save-excursion
(evil-prev-flyspell-error count)
(ispell-word)))
2022-02-17 19:38:05 +00:00
(evil-define-key 'insert text-mode-map (kbd "M-s M-s") 'ha-fix-last-spelling)
2021-12-29 19:09:43 +00:00
(ha-local-leader :keymaps 'text-mode-map
"s" '(:ignore t :which-key "spellcheck")
2022-02-17 19:38:05 +00:00
"s s" '("correct last misspell" . ha-fix-last-spelling)
"s b" '("check buffer" . flyspell-buffer)
"s c" '("correct word" . flyspell-auto-correct-word)
2021-12-29 19:09:43 +00:00
"s p" '("previous misspell" . evil-prev-flyspell-error)
"s n" '("next misspell" . evil-next-flyspell-error)))
#+END_SRC
2022-02-17 17:50:34 +00:00
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 19:38:05 +00:00
A real issue I often face is I can be typing along and notice a mistake after entering a more words. Since this happens in /insert/ mode, I have bound ~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 =flyspell-auto-correct-word= .
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:
#+BEGIN_SRC emacs-lisp
(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)
#+END_SRC
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 ]]:
#+BEGIN_SRC emacs-lisp
(use-package powerthesaurus
:bind ("M-T" . powerthesaurus-lookup-dwim)
:config
(ha-local-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)))
#+END_SRC
2022-03-09 06:01:19 +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:
2021-12-29 19:43:41 +00:00
#+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
2022-02-17 17:53:06 +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 ]],
#+BEGIN_SRC emacs-lisp
(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)))
#+END_SRC
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= :
#+BEGIN_SRC emacs-lisp :tangle no
(use-package writegood-mode
:hook ((org-mode . writegood-mode)))
#+END_SRC
2021-11-02 00:27:14 +00:00
2022-03-09 06:01:19 +00:00
We install the =write-good= NPM:
#+BEGIN_SRC shell
npm install -g write-good
#+END_SRC
And check that the following works:
2022-03-15 16:54:30 +00:00
#+BEGIN_SRC sh :results output
2022-03-09 06:01:19 +00:00
write-good --text="So it is what it is."
#+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:
2021-11-02 00:27:14 +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))
(add-to-list 'flycheck-checkers 'vale 'append))
2022-03-09 06:01:19 +00:00
#+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
brew install proselint
#+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
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
}
2021-12-30 02:52:00 +00:00
#+END_SRC
2022-03-09 06:01:19 +00:00
And tell [[https://www.flycheck.org/ ][flycheck ]] to use this:
#+BEGIN_SRC emacs-lisp
(use-package flycheck
:config (add-to-list 'flycheck-checkers 'proselint))
#+END_SRC
2022-03-25 18:02:02 +00:00
** Distraction-Free Writing
*** 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
#+BEGIN_SRC emacs-lisp
(use-package writeroom-mode
:hook (writeroom-mode-disable . winner-undo)
:config
(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)))
2021-11-02 00:27:14 +00:00
#+END_SRC
2022-03-25 18:02:02 +00:00
*** Olivetti
The [[https://github.com/rnkn/olivetti ][olivetti project ]] sets wide margins and centers the text. It isn’ t better than Writeroom, however, 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
*** 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
(use-package logos
:straight (:type git :protocol ssh :host gitlab :repo "protesilaos/logos")
:init
(setq logos-outlines-are-pages t
logos-outline-regexp-alist
`((emacs-lisp-mode . "^;;;+ ")
(org-mode . "^\\*+ + ")
(t . ,(or outline-regexp logos--page-delimiter))))
;; These apply when `logos-focus-mode' is enabled. Their value is
;; buffer-local.
(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))
(let ((map global-map))
(define-key global-map [remap narrow-to-region] #'logos-narrow-dwim)
(evil-define-key 'normal map (kbd "g [") 'logos-backward-page-dwim)
(evil-define-key 'normal map (kbd "g ]") 'logos-forward-page-dwim)))
#+END_SRC
2021-11-02 00:27:14 +00:00
* Technical Artifacts :noexport:
2022-03-09 06:01:19 +00:00
Let's provide a name, to allow =require= to work:
2021-11-02 00:27:14 +00:00
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-org)
;;; ha-org.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 configuring org-mode and those files.
#+PROPERTY : header-args:sh :tangle no
2021-11-17 18:32:02 +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