Let's get back to an easy way to focus my workflow.
22 KiB
General Org-Mode Configuration
A literate programming file for configuring org-mode and those files.
Use Package
Org is a large complex beast with a gazillion settings, so I discuss these later in this document.
(use-package org
;; :straight (:type built-in) ; Use with problems from 9.4.4 version
:mode ("\\.org" . org-mode) ; Addresses an odd warning
:init
<<variables>>
<<org-todo>>
:config
<<ha-org-leader>>
<<visual-hook>>
<<text-files>>
<<org-font-lock>>
<<no-flycheck-in-org>>
<<ob-languages>>
<<ox-exporters>>
<<org-return-key>>
<<global-keybindings>>
<<org-keybindings>>)
One other helper routine is a general
macro for org-mode files:
(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")
Initialization Section
Org is an important part of my Emacs world, and with a lot of customization (even though Spacemacs and Doom do a good job getting things started).
(setq org-return-follows-link t
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.
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
;; Speed Commands: If point is at the beginning of a headline or code
;; block in org-mode, single keys do fun things. See
;; org-speed-command-help for details (or hit the ? key at a headline).
org-use-speed-commands t
org-export-with-sub-superscripts nil
org-directory "~/personal"
org-default-notes-file "~/personal/general-notes.txt"
org-enforce-todo-dependencies t ; Can't close a task until subtasks are done
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
org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t)
Configuration Section
I pretend that my org files are word processing files that wrap automatically:
(add-hook 'org-mode-hook #'visual-line-mode)
Files that end in .txt
are still org files to me:
(add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
(add-to-list 'safe-local-variable-values '(org-content . 2))
Note: Many of the files that I edit close some, but not all, of the headers using a file variable. Let's allow that to not insist that I 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 only if we are at the end of the line.
(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',
otherwise, just call `org-return' as usual."
(interactive)
(if (eolp)
(ha-org-special-return)
(org-return)))
And bind it to the Return key:
(define-key org-mode-map (kbd "RET") #'ha-org-return)
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. - On a link, call
org-return
in order to open it. - On a header? Let's create a new header (or maybe not).
- In a table? Let's create a new row.
- If we are really in a list, we can create a new item.
I really should break this function into smaller bits …
(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.
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)))))
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 only one blank line.
(defun org-really-in-item-p ()
"Similar to `org-in-item-p', however, 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))))
The org API allows getting the context associated with current element. However, this could be a line-level symbol, like paragraph or list-item
only if 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:
(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))
Tasks
I need to add a blocked state:
(setq org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)")
(sequence "BLOCKED(b)" "|" "CANCELLED(c)")))
And I would like to have cute little icons for those states:
(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 quickly 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) "•")))))))
Meetings
I've notice that while I really like taking notes in a meeting, I don't always like the multiple windows I have opened, so I created this function that I can easily call to eliminate distractions during a meeting.
(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
(narrow-to-region (region-beginning) (region-end)) ; Only show that region
(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."))
Of course, I need an 'undo' feature when the meeting is over…
(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
Misc
Babel Blocks
Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off.
(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)
And turn on ALL the languages:
(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)))
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:
(defun ha-org-next-image-number (&optional prefix)
(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)))))
In a PlantUML Block
To make the snippets more context aware, this predicate
(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)))
Keybindings
Keybindings available to all file buffers:
(ha-leader
"o l" '("store link" . org-store-link)
"o x" '("org capture" . org-capture)
"o c" '("clock out" . org-clock-out))
Bindings specific to org files:
(ha-org-leader
"e" '("exports" . org-export-dispatch)
"I" '("insert id" . org-id-get-create)
"l" '("insert link" . org-insert-link)
"N" '("store link" . org-store-link)
"o" '("goto link" . ace-link-org)
"P" '("set property" . org-set-property)
"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)
"n" '(:ignore t :which-key "narrow")
"n s" '("subtree" . org-narrow-to-subtree)
"n b" '("block" . org-narrow-to-block)
"n e" '("element" . org-narrow-to-element)
"n w" '("widen" . widen))
Oh, and we'll use ace-link for quickly jumping:
(use-package ace-link
:after org
:config
(define-key org-mode-map (kbd "s-o") 'ace-link-org))
Supporting Packages
Exporters
Limit the number of exporters to just the ones that I would use:
(setq org-export-backends '(ascii html icalendar md odt))
I have a special version of tweaked Confluence exporter for my org files:
(use-package ox-confluence
:after org
:straight nil ; Located in my "elisp" directory
:config
(ha-org-leader
"E" '("to confluence" . ox-export-to-confluence)))
And Graphviz configuration using graphviz-dot-mode:
(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))
And we can install company support:
(use-package company-graphviz-dot)
Spell Checking
Let's hook some spell-checking into org files, and actually all text files. We'll use flyspell mode to highlight the misspelled words, and use the venerable ispell for correcting (as it is less mousy).
(use-package flyspell
:hook (text-mode . flyspell-mode)
:bind ("M-S" . ha-fix-last-spelling)
: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)))
: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)))
(ha-local-leader :keymaps 'text-mode-map
"s" '(:ignore t :which-key "spellcheck")
"s c" '("correct last misspell" . ha-fix-last-spelling)
"s p" '("previous misspell" . evil-prev-flyspell-error)
"s n" '("next misspell" . evil-next-flyspell-error)))
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 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:
(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)
The end result? No misspellings. Isn‘t this nice?
Of course I need a thesaurus, and I'm installing powerthesaurus:
(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)))
The key-bindings, keystrokes, key-connections work well with just M-T
(notice the Shift), but to jump to specifics, we use a leader. However, the definitions do not work, so let's use abo-abo's define-word project:
(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)))
Focused Work
CLOCK: [2022-02-11 Fri 11:05]–[2022-02-11 Fri 11:21] => 0:16
I've been working on my own approach to focused work,
(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)))
Writegood
The writegood-mode highlights passive and weasel words as typed. Shame it doesn't check for dangled prepositions.
(use-package writegood-mode
:hook ((org-mode . writegood-mode)))
Writeroom
For a complete focused, distraction-free environment, for writing or just concentrating, I'm using Writeroom-mode:
(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)))