Still having trouble getting the notebook working, but at this point, I really just want Org to look cooler. :-D
26 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
:mode ("\\.org" . org-mode) ; Addresses an odd warning
:init
<<variables>>
<<org-todo>>
<<ob-configuration>>
:config
<<ha-org-leader>>
<<visual-hook>>
<<text-files>>
<<org-font-lock>>
<<no-flycheck-in-org>>
<<ob-languages>>
<<ob-graphviz>>
<<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
Begin by initializing these org variables:
(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.
org-imenu-depth 4
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
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 without completed subtasks
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: Org mode files with the org-content
variable setting will collapse two levels headers. Let's allow that without the need to approve that.
Better Return
Hitting the Return
key in an org file should format the following line based on context. For instance, at the end of a list, insert a new item.
We begin with the interactive function that calls our code if we are at the end of the line.
(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, `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
and open it. - On a header? Create a new header.
- In a table? Create a new row.
- In a list, create a new item.
I 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 one blank line.
(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))))
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:
(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 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 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.
(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)) ; 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
I use org-babel (obviously) and don’t need confirmation before evaluating a block:
(setq org-confirm-babel-evaluate nil
org-src-fontify-natively t
org-src-tab-acts-natively t)
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)))
The graphviz project can be written in org blocks, and then rendered as an image:
(add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot"))
For example:
digraph G {
A -> B -> E;
A -> D;
A -> C;
E -> F;
E -> H
D -> F;
A -> H;
E -> G;
}
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
Global 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 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 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.
(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)))
(evil-define-key 'insert text-mode-map (kbd "M-s M-s") 'ha-fix-last-spelling)
(ha-local-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)))
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.
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
.
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, 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 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)))
Grammar and Prose Linting
Flagging cliches, weak phrasing and other poor grammar choices.
Writegood
The writegood-mode is effective at highlighting passive and weasel words, but isn’t integrated into flycheck
:
(use-package writegood-mode
:hook ((org-mode . writegood-mode)))
We install the write-good
NPM:
npm install -g write-good
And check that the following works:
write-good --text="So it is what it is."
Now, let’s connect it to flycheck:
(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))
Proselint
With overlapping goals to write-good
, the 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.
brew install proselint
Next, create a configuration file, ~/.config/proselint/config
file, to turn on/off checks:
{
"checks": {
"typography.diacritical_marks": false,
"annotations.misc": false,
"consistency.spacing": false
}
}
And tell flycheck to use this:
(use-package flycheck
:config (add-to-list 'flycheck-checkers 'proselint))
Distraction-Free Writing
Write-room
For a complete focused, distraction-free environment, for writing or 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)))
Olivetti
The olivetti project sets wide margins and centers the text. It isn’t better than Writeroom, however, it works well with Logos (below).
(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)))
Logos
Trying out Protesilaos Stavrou’s logos project as a replacement for Writeroom-mode:
(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)))