hamacs/ha-org-word-processor.org
2023-12-19 20:15:26 -08:00

447 lines
20 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+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 <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.
* 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= doesnt really work well with variable-width fonts, so lets 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 isnt 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]], Ive 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 orgs 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, lets 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 lets 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
This process allows us to use =variable-pitch= mode for all org documents.
#+begin_src emacs-lisp
(use-package org
;; TODO: Using the latest org-mode
;; :straight (:type built-in)
:hook (org-mode . variable-pitch-mode))
#+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
:init
(defvar ha-org-present-mode-line mode-line-format "Cache previous mode-line format state")
:config
(defun org-blocks-hide-headers ()
"Make the headers and other block metadata invisible. See `org-blocks-show-headers'."
(add-to-invisibility-spec 'ha-org-block-headers)
(defun hide-this (regexp)
(goto-char (point-min))
(while (re-search-forward regexp nil t)
(let ((start (match-beginning 0)) (end (1+ (match-end 0))))
(overlay-put (make-overlay start end) 'invisible 'ha-org-block-headers))))
(defun hide-these (patterns)
(when patterns
(hide-this (car patterns))
(hide-these (cdr patterns))))
(save-excursion
(hide-these (list (rx bol (zero-or-more space)
"#+" (or "begin" "end") "_"
(one-or-more any) eol)
(rx bol (zero-or-more space)
"#+" (or "name" "header" "results" "property" "options"
"filetags") ":"
(zero-or-more any) eol)
(rx bol (zero-or-more space)
":" (or "properties" "header-args" "end") ":"
(zero-or-more any) eol)))))
(defun org-blocks-show-headers ()
"Un-invisibilize the headers and other block metadata invisible.
In other words, this undoes what `org-blocks-hide-headers' did."
(remove-from-invisibility-spec 'ha-org-block-headers))
(defun org-present-start ()
(goto-char (point-min)) (re-search-forward (rx bol "*"))
(org-blocks-hide-headers)
(org-present-big)
(setq mode-line-format nil)
(org-display-inline-images)
(blink-cursor-mode -1)
(setq cursor-type nil))
(defun org-present-end ()
(org-present-small)
(org-blocks-show-headers)
(setq mode-line-format ha-org-present-mode-line)
(setq cursor-type t)
(blink-cursor-mode 1)
(org-present-read-write))
:hook
(org-present-mode . org-present-start)
(org-present-mode-quit . 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]]), Im 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<eol>", "#+FOO<eol>".
;;
;; 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: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