#+title: Org As A Word Processor #+author: Howard X. Abrams #+date: 2020-09-10 #+tags: emacs org A literate programming file for making Org file more readable. #+begin_src emacs-lisp :exports none ;;; ha-org-word-processor --- Making Org file more readable. -*- lexical-binding: t; -*- ;; ;; © 2020-2023 Howard X. Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: September 10, 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-word-processor.org ;; Using `find-file-at-point', and tangle the file to recreate this one . ;; ;;; Code: #+end_src * Introduction I like having org-mode files look more like a word processor than having it look like programming code. But that is me. The end results: [[file:screenshots/org-as-word-processor.png]] * General Org Settings Since I use ellipsis in my writing… to /change/ how org renders a collapsed heading. #+begin_src emacs-lisp (setq org-pretty-entities t org-ellipsis "⤵" ; …, ➡, ⚡, ▼, ↴, , ∞, ⬎, ⤷, ⤵ org-agenda-breadcrumbs-separator " ❱ " org-catch-invisible-edits 'show-and-error org-special-ctrl-a/e t ; Note: Need to get this working with Evil! org-src-fontify-natively t ; Pretty code blocks org-hide-emphasis-markers t) #+end_src Oh, and as I indent lists, they should change the /bulleting/ in a particular sequence. If I begin with an =*= asterisk, I walk down the chain, but with the dashed bullets (my default choice), I stay with dashed bullets. Numeric bullets should cycle: #+begin_src emacs-lisp (setq org-list-demote-modify-bullet '(("*" . "+") ("+" . "-") ("-" . "-") ("1." . "a.") ("a." . "1."))) #+end_src The =org-indent-indentation-per-level=, which defaults to =2= doesn’t really work well with variable-width fonts, so let’s make the spaces at the beginning of the line fixed: #+begin_src emacs-lisp (use-package org ;; TODO: Using the latest org-mode ;; :straight (:type built-in) :custom-face (org-indent ((t (:inherit fixed-pitch))))) #+end_src ** Typographic Quotes According to [[http://endlessparentheses.com/prettify-your-quotation-marks.html][Artur Malabarba]] of [[http://endlessparentheses.com/prettify-you-apostrophes.html][Endless Parenthesis]] blog, I type either a /straight single or double quote/, ", Emacs actually inserts Unicode rounded quotes, like “this”. This idea isn’t how a file is /displayed/, but actually how the file is /made/. Time will tell if this idea works with my auxiliary functions on my phone, like [[https://play.google.com/store/apps/details?id=com.orgzly&hl=en_US&gl=US][Orgzly]] and [[https://github.com/amake/orgro][Orgro]]. Stealing his function, and updating it a bit, so that “quotes” work to insert /rounded/ quotation marks: #+begin_src emacs-lisp (defun ha--insert-round-quotes (opening closing) "Insert rounded quotes in prose but not inside code. The OPENING and CLOSING variables are either or . Rules: • At beginning of line or after a space (in other words, single-quoting a word or phrase), insert OPENING and CLOSING string, and leave point between them. • At beginning of existing word, insert OPENING, as the assumption is to go somewhere else to insert the CLOSING character. • If looking at the CLOSING character, move past it. • Otherwise, insert an CLOSING character, as this is probably finishing the quotation. Inside a code-block, just call `self-insert-command'." (cl-flet ((insert-pair () (insert opening) (insert closing) (forward-char -1))) ;; Don't do anything special in code blocks: (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html" "example"))) (call-interactively #'self-insert-command) ;; Define some regular expressions to make the `cond' clearer: (let ((existing-word (rx word-start)) (starting-anew (rx (or bol space))) (existing-endq (rx-to-string `(seq (or "'" "\"" ,opening ,closing) (optional (any "=_/*")))))) (cond ((looking-at existing-word) (insert opening)) ((looking-back starting-anew) (insert-pair)) ((looking-at existing-endq) (goto-char (match-end 0))) (t (insert closing))))))) #+end_src Now we can take advantage of the abstraction for “double quotes”: #+begin_src emacs-lisp (defun ha-round-quotes () "Insert “” and leave point in the middle. Inside a code-block, just call `self-insert-command'. See `ha--insert-round-quotes' for rule details." (interactive) (ha--insert-round-quotes "“" "”")) (define-key org-mode-map "\"" #'ha-round-quotes) #+end_src And something similar for single quotes: #+begin_src emacs-lisp (defun ha-apostrophe () "Insert ‘’ and leave point in the middle. Inside a code-block, just call `self-insert-command'. See `ha--insert-round-quotes' for rule details." (interactive) (ha--insert-round-quotes "‘" "’")) (define-key org-mode-map "'" #'ha-apostrophe) #+end_src *Note:* I still need to worry about how quotes affect [[file:ha-org.org::*Spell Checking][spell checking]]. What would be nice, is that if I end quotes using the functions above, that if I immediately delete, I delete both pairs. #+begin_src emacs-lisp (defun ha-delete-quote-pairs (&optional N) "If positioned between two quote symbols, delete the last. Used as advice to `org-delete-backward-char' function." (when (and (looking-at (rx (any "\"" "'" "`" "”" "’"))) (looking-back (rx (any "\"" "'" "`" "“" "‘")))) (org-delete-char N))) (advice-add #'org-delete-backward-char :before #'ha-delete-quote-pairs) #+end_src Can we do the same with ellipses? #+begin_src emacs-lisp (defun ha-insert-dot-or-ellipsis () "Insert a `.' unless two have already be inserted. In this case, insert an ellipsis instead." (interactive) (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html" "example"))) (call-interactively #'self-insert-command) (cond ((looking-back (rx "…")) (delete-backward-char 1) (insert "⋯")) ((looking-back (rx "..")) (delete-backward-char 2) (insert "…")) (t (insert "."))))) (define-key org-mode-map "." #'ha-insert-dot-or-ellipsis) #+end_src After reading [[https://www.punctuationmatters.com/en-dash-em-dash-hyphen][this essay]], I’ve gotten obsessive with elongating dashes. In this case, typing a dash surrounded with spaces, e.g. something – like this, we convert them to [[https://www.compart.com/en/unicode/U+2013][en dash]]. But if I type two dashes in a row—which identifies an emphasized clause—I can convert it directly to [[https://www.compart.com/en/unicode/U+2014][em dash]]. Continually typing a dash replaces that character with longer and longer dashes⸺ #+begin_src emacs-lisp (defun ha-insert-space () "Insert a space unless previously typed a dash. In this case, insert an n-dash instead." (interactive) (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html" "example"))) (call-interactively #'self-insert-command) (if (or (looking-back (rx line-start (one-or-more space) "-")) (looking-back (rx (not "-")))) (call-interactively #'self-insert-command) (delete-backward-char 1) (insert "– ")))) ; Replace dash with en-dash + space (define-key org-mode-map " " #'ha-insert-space) (defun ha-insert-long-dash () "Insert a `-' unless other dashes have already be inserted. In this case, insert an n-dash or m-dashes instead." (interactive) (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html" "example"))) (call-interactively #'self-insert-command) (cond ((looking-back (rx "-")) (delete-backward-char 1) (insert "—")) ((looking-back (rx "—")) (delete-backward-char 1) (insert "⸺")) ((looking-back (rx "⸺")) (delete-backward-char 1) (insert "⸻")) ((looking-back (rx "⸻")) (delete-backward-char 1) (insert "------------------------------------------------------------")) (t (insert "-"))))) (define-key org-mode-map "-" #'ha-insert-long-dash) #+end_src The /issue/ is how do we deal with org’s dashed bullets? In this case, we want to insert an actual dash, but elsewhere, we /visually/ display the dash as a more emphasized glyph. ** Ligatures Well, using the =composition-function-table=, we can finally get some ligatures to improve readability without Harfbuzz. #+begin_src emacs-lisp (defun ha-textual-litagures () "Non-programming litagures for readable and text-derived modes." (set-char-table-range composition-function-table ?f '(["\\(?:ff?[fijlt]\\)" 0 font-shape-gstring])) (set-char-table-range composition-function-table ?T '(["\\(?:Th\\)" 0 font-shape-gstring]))) (add-hook 'text-mode-hook #'ha-textual-litagures) #+end_src This is now fine and ffantastic! * Org Beautify I really want to use the Org Beautify package, but it overrides my darker themes (and all I really want is headlines to behave). First step is to make all Org header levels to use the variable font, and be the same color as the default text: #+begin_src emacs-lisp (when window-system (let ((default-color (face-attribute 'default :foreground))) (dolist (face '(org-level-1 org-level-2 org-level-3 org-level-4 org-level-5 org-level-6 org-level-7 org-level-8)) (set-face-attribute face nil :height 1.1 :foreground default-color :weight 'bold :font ha-variable-header-font)))) #+end_src Next, we just need to change the header sizes: #+begin_src emacs-lisp (when window-system (set-face-attribute 'org-level-1 nil :height 2.2) (set-face-attribute 'org-level-2 nil :height 1.8) (set-face-attribute 'org-level-3 nil :height 1.4) (set-face-attribute 'org-level-4 nil :height 1.2)) #+end_src While we are at it, let’s make sure the code blocks are using my fixed with font: #+begin_src emacs-lisp (when window-system (dolist (face '(org-block org-code org-verbatim org-table org-drawer org-table org-formula org-special-keyword org-block org-property-value org-document-info-keyword)) (set-face-attribute face nil :inherit 'fixed-pitch :height 0.9))) (set-face-attribute 'org-table nil :height 1.0) (set-face-attribute 'org-formula nil :height 1.0) #+end_src Not sure why the above code removes the color of =org-verbatim=, so let’s make it stand out slightly: #+begin_src emacs-lisp (set-face-attribute 'org-verbatim nil :foreground "#aaaacc") #+end_src And some slight adjustments to the way blocks are displayed: #+begin_src emacs-lisp (set-face-attribute 'org-block-begin-line nil :background "#282828") (set-face-attribute 'org-block nil :height 0.95) (set-face-attribute 'org-block-end-line nil :background "#262626") #+end_src And decrease the prominence of the property drawers: #+begin_src emacs-lisp (set-face-attribute 'org-drawer nil :height 0.8) (set-face-attribute 'org-property-value nil :height 0.85) (set-face-attribute 'org-special-keyword nil :height 0.85) #+end_src * Org Modern The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a lot of what I was doing in this file. #+begin_src emacs-lisp (use-package org-modern :straight (:host github :repo "minad/org-modern") :hook ((org-mode . org-modern-mode) (org-agenda-finalize . org-modern-agenda))) #+end_src I like the smaller code blocks as well as the <2022-06-16 Thu> timestamps. * Checkboxes According to an idea by [[https://jft.home.blog/2019/07/17/use-unicode-symbol-to-display-org-mode-checkboxes/][Huy Trần]], (and expanded by the [[https://github.com/minad/org-modern][org-modern]] project), we can prettify the list checkboxes. To make completed tasks more distinguishable, he changed the colors: #+begin_src emacs-lisp (defface org-checkbox-done-text '((t (:foreground "#71696A" :strike-through t))) "Face for the text part of a checked org-mode checkbox.") (font-lock-add-keywords 'org-mode `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 1 'org-checkbox-done-text prepend)) 'append) #+end_src * Padding The [[https://github.com/TonCherAmi/org-padding][org-padding]] project looks places extra space before and after headers and blocks (essentially leading), to create a more word-processor-y experience. Great idea, however, I have spent a lot of extra time entering blank lines before and after my headers and blocks: #+begin_src emacs-lisp (use-package org-padding :straight (:host github :repo "TonCherAmi/org-padding") :hook (org-mode . org-padding-mode) :config (setq org-padding-block-begin-line-padding '(0.5 . 0.3) org-padding-block-end-line-padding '(0.1 . 0.5) org-padding-heading-padding-alist '((4.0 . 1.5) (3.0 . 0.5) (3.0 . 0.5) (3.0 . 0.5) (2.5 . 0.5) (2.0 . 0.5) (1.5 . 0.5) (0.5 . 0.5)))) #+end_src However, I'm just going to have to write a function to clean this. #+begin_src emacs-lisp :tangle no (defun ha-remove-superfluous-org-padding () (interactive) (goto-char (point-min)) (ha-remove-org-header-padding) (goto-char (point-min)) (ha-remove-org-block-padding)) (defun ha-remove-org-header-padding () ;; (goto-char (point-min)) (while (re-search-forward (rx (optional bol (zero-or-more space) eol "\n") (group bol (one-or-more "*") (one-or-more space) (one-or-more any) "\n") (optional bol (zero-or-more space) eol "\n")) nil t) (replace-match (match-string 1) nil :no-error))) (defun ha-remove-org-block-padding () ;; (goto-char (point-min)) (while (re-search-forward (rx (optional bol (zero-or-more space) eol "\n") (group bol (zero-or-more space) "#+BEGIN" (one-or-more any) eol "\n" (zero-or-more (group bol (zero-or-more any) eol "\n")) bol (zero-or-more space) "#+END" (zero-or-more any) eol "\n") (optional bol (zero-or-more space) eol "\n")) nil t) (replace-match (match-string 1) nil :no-error))) #+end_src Now that is some complicated regular expressions. * Presentations Used to use [[https://github.com/takaxp/org-tree-slide][org-tree-slide]] for showing org files as presentations. Converted to use [[https://github.com/rlister/org-present][org-present]]. I love the /hooks/ as that makes it easier to pull out much of my =demo-it= configuration. My concern with =org-present= is that it only jumps from one top-level to another top-level header. #+begin_src emacs-lisp (use-package org-present :config (defvar ha-org-present-mode-line mode-line-format "Cache previous mode-line format state") (defun ha-org-blocks-hide-headers () "Make the headers and other block metadata invisible. See `ha-org-blocks-show-headers'." (let ((pattern (rx bol (zero-or-more space) (or ":" "#") (zero-or-more any) eol))) (save-excursion (goto-char (point-min)) (while (re-search-forward pattern nil t) (let* ((start (1+ (match-beginning 0))) (end (1+ (match-end 0))) (ovlay (make-overlay start end))) (overlay-put ovlay 'invisible t)))))) (defun ha-org-blocks-show-headers () "Un-invisibilize the headers and other block metadata invisible. In other words, this undoes what `ha-org-blocks-hide-headers' did." (delete-all-overlays)) (defun ha-org-present-start () (unless ha-org-present-mode-line (setq ha-org-present-mode-line mode-line-format)) (goto-char (point-min)) (re-search-forward (rx bol "*")) (ha-org-blocks-hide-headers) (org-present-big) (org-display-inline-images) (setq mode-line-format nil) (sit-for 3) ; Wait for the cursor to stop blinking (org-present-hide-cursor)) (defun ha-org-present-end () (org-present-small) (ha-org-blocks-show-headers) (setq mode-line-format ha-org-present-mode-line) (org-present-show-cursor)) :hook (org-present-mode . ha-org-present-start) (org-present-mode-quit . ha-org-present-end)) #+end_src * Technical Artifacts :noexport: Note, according to [[https://www.reddit.com/r/emacs/comments/vahsao/orgmode_use_capitalized_property_keywords_or/][this discussion]] (and especially [[https://scripter.co/org-keywords-lower-case/][this essay]]), I’m switching over to lower-case version of org properties. Using this helper function: #+begin_src emacs-lisp (defun modi/lower-case-org-keywords () "Lower case Org keywords and block identifiers. Example: \"#+TITLE\" -> \"#+title\" \"#+BEGIN_EXAMPLE\" -> \"#+begin_example\" Inspiration: https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0." (interactive) (save-excursion (goto-char (point-min)) (let ((case-fold-search nil) (count 0) ;; All keywords can be found with this expression: ;; (org-keyword-re "\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)") ;; Match examples: "#+foo bar", "#+foo:", "=#+foo=", "~#+foo~", ;; "‘#+foo’", "“#+foo”", ",#+foo bar", ;; "#+FOO_bar", "#+FOO". ;; ;; Perhap I want the #+begin_src and whatnot: (org-keyword-re (rx line-start (optional (zero-or-more space)) "#+" (group (or "BEGIN" "END") "_" (one-or-more alpha))))) (while (re-search-forward org-keyword-re nil :noerror) (setq count (1+ count)) (replace-match (downcase (match-string-no-properties 1)) :fixedcase nil nil 1)) (message "Lower-cased %d matches" count)))) #+end_src Let's provide a name so we can =require= this file: #+begin_src emacs-lisp :exports none (provide 'ha-org-word-processor) ;;; ha-org-word-processor.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 making Org file more readable. #+PROPERTY: header-args:sh :tangle no #+PROPERTY: header-args:emacs-lisp :tangle yes #+PROPERTY: header-args :results none :eval no-export :comments no #+OPTIONS: num:nil toc:t todo:nil tasks:nil tags:nil date:nil #+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil #+INFOJS_OPT: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js