2021-11-02 00:27:14 +00:00
#+TITLE : Org As A Word Processor
#+AUTHOR : Howard X. Abrams
#+DATE : 2020-09-10
#+FILETAGS : :emacs:
A literate programming file for making Org file more readable.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp :exports none
2022-03-09 18:45:37 +00:00
;;; ha-org-word-processor --- Making Org file more readable. -*- lexical-binding: t; -* -
;;
2023-02-23 17:35:36 +00:00
;; © 2020-2023 Howard X. Abrams
2022-06-18 00:25:47 +00:00
;; Licensed under a Creative Commons Attribution 4.0 International License.
2022-03-09 18:45:37 +00:00
;; 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:
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Introduction
2022-06-18 00:25:47 +00:00
I like having org-mode files look more like a word processor than having it look like programming code. But that is me.
2021-11-02 00:27:14 +00:00
* General Org Settings
2022-03-10 01:14:21 +00:00
Since I use ellipsis in my writing… to /change/ how org renders a collapsed heading.
2021-11-02 00:27:14 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2021-11-02 00:27:14 +00:00
(setq org-pretty-entities t
org-ellipsis "⤵" ; …, ➡, ⚡, ▼, ↴, , ∞, ⬎, ⤷, ⤵
org-agenda-breadcrumbs-separator " ❱ "
2022-03-18 21:21:18 +00:00
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
2021-11-02 00:27:14 +00:00
org-hide-emphasis-markers t)
2022-06-18 00:25:47 +00:00
#+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:
2022-03-18 21:21:18 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 21:21:18 +00:00
(setq org-list-demote-modify-bullet '(("*" . "+") ("+ " . "-") ("-" . "-")
("1." . "a.") ("a." . "1.")))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-04 22:04:20 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-09 16:02:37 +00:00
(use-package org
2023-03-13 04:25:23 +00:00
:straight (:type built-in)
2022-04-17 05:52:43 +00:00
:custom-face (org-indent ((t (:inherit fixed-pitch)))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-17 17:50:34 +00:00
** Typographic Quotes
2022-04-11 22:34:56 +00:00
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 ]].
2022-02-17 17:50:34 +00:00
2022-06-18 00:25:47 +00:00
Stealing his function, and updating it a bit, so that “quotes” work to insert /rounded/ quotation marks:
#+begin_src emacs-lisp
2022-04-11 22:34:56 +00:00
(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)
2022-04-17 05:52:43 +00:00
(org-in-block-p '("src" "latex" "html" "example")))
2022-04-11 22:34:56 +00:00
(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)))
2022-04-17 05:52:43 +00:00
(existing-endq
(rx-to-string `(seq (or "'" "\"" ,opening ,closing)
(optional (any "=_/*"))))))
2022-04-11 22:34:56 +00:00
(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)))))))
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-11 22:34:56 +00:00
Now we can take advantage of the abstraction for “double quotes”:
2022-02-17 17:50:34 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-11 22:34:56 +00:00
(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)
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-17 17:50:34 +00:00
2022-04-11 22:34:56 +00:00
And something similar for single quotes:
2022-02-17 17:50:34 +00:00
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-11 22:34:56 +00:00
(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)
2022-06-18 00:25:47 +00:00
#+end_src
2022-02-17 17:50:34 +00:00
*Note:* I still need to worry about how quotes affect [[file:ha-org.org::*Spell Checking ][spell checking ]].
2022-04-11 22:34:56 +00:00
What would be nice, is that if I end quotes using the functions above, that if I immediately delete, I delete both pairs.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-11 22:34:56 +00:00
(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)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-17 05:52:43 +00:00
Can we do the same with ellipses?
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-17 05:52:43 +00:00
(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)
2022-06-18 00:25:47 +00:00
#+end_src
2022-04-17 05:52:43 +00:00
Now I’ m getting obsessive with elongating dashes:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-04-17 05:52:43 +00:00
(defun ha-insert-long-dash ()
"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 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)
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* 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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(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))
2022-04-04 22:04:20 +00:00
(set-face-attribute face nil :height 1.1
2023-02-01 16:09:01 +00:00
:foreground default-color :weight 'bold
:font ha-variable-header-font))))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
Next, we just need to change the header sizes:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(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)
2022-04-04 22:04:20 +00:00
(set-face-attribute 'org-level-4 nil :height 1.2))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-09 06:01:59 +00:00
While we are at it, let’ s make sure the code blocks are using my fixed with font:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(when window-system
2022-06-16 21:09:10 +00:00
(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))
2022-04-09 16:03:34 +00:00
(set-face-attribute face nil :inherit 'fixed-pitch :height 0.9)))
2022-04-01 18:29:45 +00:00
(set-face-attribute 'org-table nil :height 1.0)
(set-face-attribute 'org-formula nil :height 1.0)
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-18 20:50:40 +00:00
Not sure why the above code removes the color of =org-verbatim= , so let’ s make it stand out slightly:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(set-face-attribute 'org-verbatim nil :foreground "#aaaacc")
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-18 20:50:40 +00:00
And some slight adjustments to the way blocks are displayed:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(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")
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-18 20:50:40 +00:00
And decrease the prominence of the property drawers:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-24 03:57:09 +00:00
(set-face-attribute 'org-drawer nil :height 0.8)
2022-03-18 20:50:40 +00:00
(set-face-attribute 'org-property-value nil :height 0.85)
(set-face-attribute 'org-special-keyword nil :height 0.85)
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-18 20:50:40 +00:00
This process allows us to use =variable-pitch= mode for all org documents.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-18 20:50:40 +00:00
(use-package org
2023-03-13 04:25:23 +00:00
:straight (:type built-in)
2022-03-18 20:50:40 +00:00
:hook (org-mode . variable-pitch-mode))
2022-06-18 00:25:47 +00:00
#+end_src
2022-06-16 21:09:10 +00:00
* 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.
2022-03-24 18:55:25 +00:00
* Checkboxes
2022-06-16 21:09:10 +00:00
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:
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-24 18:55:25 +00:00
(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)
2022-06-16 21:09:10 +00:00
#+end_src
2022-03-24 18:55:25 +00:00
* 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:
2021-11-02 00:27:14 +00:00
2022-06-16 21:09:10 +00:00
#+begin_src emacs-lisp
(use-package org-padding
2022-08-09 16:57:20 +00:00
:straight (:host github :repo "TonCherAmi/org-padding")
2022-06-16 21:09:10 +00:00
: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
2022-03-24 18:55:25 +00:00
However, I'm just going to have to write a function to clean this.
2022-06-16 21:09:10 +00:00
#+begin_src emacs-lisp :tangle no
2022-04-01 18:39:11 +00:00
(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)))
2022-06-18 00:25:47 +00:00
#+end_src
2022-03-24 18:55:25 +00:00
Now that is some complicated regular expressions.
2021-11-02 00:27:14 +00:00
* Presentations
2022-03-21 23:58:00 +00:00
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.
2022-06-18 00:25:47 +00:00
#+begin_src emacs-lisp
2022-03-21 23:58:00 +00:00
(use-package org-present
2021-11-02 00:27:14 +00:00
:init
2022-03-21 23:58:00 +00:00
(defvar ha-org-present-mode-line mode-line-format "Cache previous mode-line format state")
2021-11-02 00:27:14 +00:00
:config
2022-03-21 23:58:00 +00:00
(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
2022-06-18 00:25:47 +00:00
(hide-this (car patterns))
(hide-these (cdr patterns))))
2022-03-21 23:58:00 +00:00
(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)
2022-04-04 02:51:56 +00:00
(setq mode-line-format ha-org-present-mode-line)
2022-03-21 23:58:00 +00:00
(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))
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
* Technical Artifacts :noexport:
2022-06-18 00:25:47 +00:00
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<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
2022-06-16 21:09:10 +00:00
(provide 'ha-org-word-processor)
;;; ha-org-word-processor.el ends here
2022-06-18 00:25:47 +00:00
#+end_src
2021-11-02 00:27:14 +00:00
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