437f707deb
At least, for the moment.
421 lines
19 KiB
Org Mode
421 lines
19 KiB
Org Mode
#+title: Org As A Word Processor
|
||
#+author: Howard X. Abrams
|
||
#+date: 2020-09-10
|
||
#+tags: emacs org
|
||
#+startup: inlineimages
|
||
|
||
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 <http://gitlab.com/howardabrams>
|
||
;; 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
|
||
** Markup View
|
||
The variable, =org-hide-emphasis-markers=, is key to pretending that Emacs can be a word processor, however, since the org markup controls aren’t viewable, I find it challenging at times, to change that. The [[https://github.com/awth13/org-appear][org-appear project]] seeks to fix this by showing the markup when the point is nearby:
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-appear
|
||
:straight (:type git :host github :repo "awth13/org-appear")
|
||
:init (setq org-appear-trigger 'manual)
|
||
:hook
|
||
((org-mode . (lambda ()
|
||
(add-hook 'evil-insert-state-entry-hook
|
||
#'org-appear-manual-start nil t)
|
||
(add-hook 'evil-insert-state-exit-hook
|
||
#'org-appear-manual-stop nil t)))
|
||
(org-mode . org-appear-mode)))
|
||
#+end_src
|
||
|
||
This makes the emphasis markers appear only in Evil’s insert mode.
|
||
** 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])))
|
||
|
||
(when (ha-running-on-macos?)
|
||
(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).
|
||
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun ha-word-processor-fonts ()
|
||
"Configure `org-mode' fonts and faces."
|
||
(interactive)
|
||
|
||
;; First step is to make all Org header levels to use the variable
|
||
;; font, and be the same color as the default text:
|
||
|
||
(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))))
|
||
|
||
;; Change the header sizes to show their level visually:
|
||
(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)
|
||
|
||
(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))
|
||
|
||
;; While we are at it, let’s make sure the code blocks are using my fixed with font:
|
||
(set-face-attribute 'org-table nil :height 1.0)
|
||
(set-face-attribute 'org-formula nil :height 1.0)
|
||
|
||
(set-face-attribute 'org-block-begin-line nil :height 0.9)
|
||
(set-face-attribute 'org-block-end-line nil :height 0.8)
|
||
|
||
(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
|
||
|
||
We call this function when we start:
|
||
#+begin_src emacs-lisp
|
||
(ha-word-processor-fonts)
|
||
#+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 . global-org-modern-mode)
|
||
(org-agenda-finalize . org-modern-agenda))
|
||
:custom
|
||
(org-modern-table nil))
|
||
#+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:
|
||
|
||
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
|