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
;;
;; Copyright (C) 2020 Howard X. Abrams
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams >
2021-11-18 20:12:19 +00:00
;; Maintainer: Howard X. Abrams
2021-11-02 00:27:14 +00:00
;; 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:
2021-11-10 22:24:55 +00:00
;; ~/other/hamacs/ha-org.org
2021-11-02 00:27:14 +00:00
;; 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
: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 >>
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 >>
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
:states '(normal visual)
:keymaps 'org-mode-map
:prefix "SPC m")
#+END_SRC
2021-11-02 00:27:14 +00:00
* 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).
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.
2021-11-02 00:27:14 +00:00
;; 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
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
* 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
2021-11-17 18:32:02 +00:00
*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.
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.
We begin with the interactive function that calls our code only if we are at the end of the line.
#+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',
2021-11-02 00:27:14 +00:00
otherwise, just call `org-return' as usual."
(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?
- 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 ...
#+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
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.
#+BEGIN_SRC emacs-lisp
(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))))
#+END_SRC
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:
#+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-11-02 00:27:14 +00:00
(setq org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)" )
(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
(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) "•")))))))
2021-11-17 18:32:02 +00:00
#+END_SRC :tangle no
2021-11-02 00:27:14 +00:00
** 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.
#+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
(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."))
#+END_SRC
Of course, I need an 'undo' feature when the meeting is over...
#+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
** 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.
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
*** 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
2021-11-10 01:28:58 +00:00
Keybindings available to all file buffers:
2021-11-17 18:32:02 +00:00
#+NAME : global-keybindings
#+BEGIN_SRC emacs-lisp :tangle no
2021-11-10 01:28:58 +00:00
(ha-leader
"o l" '("store link" . org-store-link)
"o x" '("org capture" . org-capture)
"o c" '("clock out" . org-clock-out))
#+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)
2021-11-10 01:27:54 +00:00
"l" '("insert link" . org-insert-link)
2021-11-15 04:42:15 +00:00
"o" '("goto link" . ace-link-org)
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
2021-11-10 01:27:54 +00:00
Oh, and we'll use [[https://github.com/abo-abo/ace-link ][ace-link ]] for quickly 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
Need a few extra exporters:
2021-11-10 01:26:42 +00:00
#+BEGIN_SRC emacs-lisp :tangle no
2021-11-14 06:18:19 +00:00
(use-package ox-md
:after org
2021-11-15 06:08:09 +00:00
:straight nil
:config
(add-to-list 'org-export-backends 'md))
2021-11-14 06:18:19 +00:00
(use-package ox-confluence
:after org
:straight nil
:load-path "~/.doom.d/elisp")
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
** Writegood
The [[https://github.com/bnbeckwith/writegood-mode ][writegood-mode ]] highlights passive and weasel words as typed. Shame it doesn't check for dangled prepositions.
#+BEGIN_SRC emacs-lisp
(use-package writegood-mode
:hook ((org-mode . writegood-mode)))
#+END_SRC
* Technical Artifacts :noexport:
Let's provide a name so that the file can be required:
#+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