#+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 ;; 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: #+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 <> <> :config <> <> <> <> <> <> <> <> <> <>) #+END_SRC 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 motion) :keymaps 'org-mode-map :prefix "SPC m") #+END_SRC * 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). #+NAME: variables #+BEGIN_SRC emacs-lisp :tangle no (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) #+END_SRC * Configuration Section I pretend that my org files are word processing files that wrap automatically: #+NAME: visual-hook #+BEGIN_SRC emacs-lisp :tangle no (add-hook 'org-mode-hook #'visual-line-mode) #+END_SRC Files that end in =.txt= are still org files to me: #+NAME: text-files #+BEGIN_SRC emacs-lisp :tangle no (add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode)) (add-to-list 'safe-local-variable-values '(org-content . 2)) #+END_SRC *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. #+BEGIN_SRC emacs-lisp (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))) #+END_SRC And bind it to the Return key: #+NAME: org-return-key #+BEGIN_SRC emacs-lisp :tangle no (define-key org-mode-map (kbd "RET") #'ha-org-return) #+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 (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))))) #+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: #+NAME: org-todo #+BEGIN_SRC emacs-lisp :tangle no (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: #+NAME: org-font-lock #+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) "•"))))))) #+END_SRC :tangle no ** 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. #+NAME: no-flycheck-in-org #+BEGIN_SRC emacs-lisp :tangle no (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) #+END_SRC And turn on ALL the languages: #+NAME: ob-languages #+BEGIN_SRC emacs-lisp :tangle no (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))) #+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 (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))))) #+END_SRC *** In a PlantUML Block To make the snippets more context aware, this predicate #+BEGIN_SRC emacs-lisp (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))) #+END_SRC ** Keybindings Keybindings available to all file buffers: #+NAME: global-keybindings #+BEGIN_SRC emacs-lisp :tangle no (ha-leader "o l" '("store link" . org-store-link) "o x" '("org capture" . org-capture) "o c" '("clock out" . org-clock-out)) #+END_SRC Bindings specific to org files: #+NAME: org-keybindings #+BEGIN_SRC emacs-lisp :tangle no (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)) #+END_SRC Oh, and we'll use [[https://github.com/abo-abo/ace-link][ace-link]] for quickly jumping: #+BEGIN_SRC emacs-lisp (use-package ace-link :after org :config (define-key org-mode-map (kbd "s-o") 'ace-link-org)) #+END_SRC * Supporting Packages ** Exporters Limit the number of exporters to just the ones that I would use: #+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 :straight nil :config (ha-org-leader "E" '("to confluence" . ox-export-to-confluence))) #+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 #+PROPERTY: header-args:emacs-lisp :tangle yes :noweb yes #+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