16 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) ; Problems with the 9.4.4 version
;; :straight (:type git :protocol ssh :repo
;; "git://git.sv.gnu.org/emacs/org-mode.git")
: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)
:keymaps 'org-mode-map
:prefix "SPC m")
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.
;; 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-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)
"l" '("insert link" . org-insert-link)
"o" '("goto link" . ace-link-org)
"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
: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)
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)))