#+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. #+BEGIN_SRC emacs-lisp :exports none ;;; ha-org-word-processor --- Making Org file more readable. -*- lexical-binding: t; -*- ;; ;; © 2020-2022 Howard X. Abrams ;; This work is 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 just 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 just 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 (set-face-attribute 'org-indent nil :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 a /straight 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 so that “quotes” just work to insert /rounded/ quotation marks: #+BEGIN_SRC emacs-lisp (define-key org-mode-map "\"" #'endless/round-quotes) (defun endless/round-quotes (italicize) "Insert “” and leave point in the middle. With prefix argument ITALICIZE, insert /“”/ instead \(meant for org-mode). Inside a code-block, just call `self-insert-command'." (interactive "P") (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html"))) (call-interactively #'self-insert-command) (if (looking-at "”[/=_\\*]?") (goto-char (match-end 0)) (when italicize (if (derived-mode-p 'markdown-mode) (insert "__") (insert "//")) (forward-char -1)) (insert "“”") (forward-char -1)))) #+END_SRC Stealing function for automatically adding a single quote (not in pairs): #+BEGIN_SRC emacs-lisp (define-key org-mode-map "'" #'endless/apostrophe) (defun endless/apostrophe (opening) "Insert ’ in prose or `self-insert-command' in code. With prefix argument OPENING, insert ‘’ instead and leave point in the middle. Inside a code-block, just call `self-insert-command'." (interactive "P") (if (and (derived-mode-p 'org-mode) (org-in-block-p '("src" "latex" "html"))) (call-interactively #'self-insert-command) (if (looking-at "['’][=_/\\*]?") (goto-char (match-end 0)) (if (null opening) (insert "’") (insert "‘’") (forward-char -1))))) #+END_SRC Quote: “From this time forward, I shouldn’t have to worry about quotes.” *Note:* I still need to worry about how quotes affect [[file:ha-org.org::*Spell Checking][spell checking]]. * 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-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-property-value org-document-info-keyword)) (set-face-attribute face nil :inherit 'fixed-pitch))) (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 This process allows us to use =variable-pitch= mode for all org documents. #+BEGIN_SRC emacs-lisp (use-package org :hook (org-mode . variable-pitch-mode)) #+END_SRC * Superstar Now that headers are noticeable, I have no reason to see a number of asterisks. I would think that this would work: #+BEGIN_SRC emacs-lisp :tangle no (setq org-hide-leading-stars t) #+END_SRC But since it doesn’t, I need to roll my own. I add a hook to standard Org, and since this is a Lisp-2, I can get away with using the same name as the variable: #+BEGIN_SRC emacs-lisp :tangle no (defun org-hide-leading-stars () (let* ((keyword `(("^\\(\\*+ \\)\\s-*\\S-" ; Do not hide empty headings! (1 (put-text-property (match-beginning 1) (match-end 1) 'invisible t) nil))))) (font-lock-add-keywords nil keyword))) (add-hook 'org-mode-hook 'org-hide-leading-stars) #+END_SRC Once I used the [[https://github.com/sabof/org-bullets][org-bullets]] package, but we've replaced it with [[https://github.com/integral-dw/org-superstar-mode][org-superstar-mode]], so the following is an improvement, especially with the sub-bullets: #+BEGIN_SRC emacs-lisp (use-package org-superstar :after org :straight (:type git :protocol ssh :host github :repo "integral-dw/org-superstar-mode") :hook (org-mode . org-superstar-mode) :init (setq ; org-superstar-headline-bullets-list '("▶") org-superstar-special-todo-items nil org-superstar-todo-bullet-alist t org-superstar-prettify-item-bullets t org-superstar-item-bullet-alist '((42 . "●") ; * (43 . "○") ; + (45 . "•")))) #+END_SRC If this works, then we get the prettier bulleted list: * Top bullets * Plus bullets: + Apples + Oranges + Bananas * Minus bullets: - Dogs - Cats - Birds * 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 as well: #+BEGIN_SRC emacs-lisp (defun ha-org-prettify-checkboxes () "Beautify Org Checkbox Symbol" (push '("[ ]" . "☐") prettify-symbols-alist) (push '("[X]" . "☐✓") prettify-symbols-alist) (push '("[-]" . "☐-") prettify-symbols-alist) (prettify-symbols-mode)) #+END_SRC And now we can attach it to a newly loaded org files: #+BEGIN_SRC emacs-lisp (add-hook 'org-mode-hook 'ha-org-prettify-checkboxes) #+END_SRC To make it more distinguishable, he also 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 * SVG-Based Prettiness While I'm intrigued with [[https://github.com/rougier][Nicolas P. Rougier]]'s [[https://github.com/rougier/notebook-mode][notebook project]], I really just want to steal their [[https://github.com/rougier/svg-lib][svg-lib project]] that allows me to create and display various SVG objects, namely tags, progress bars, progress pies and icons. Each object fits nicely in a text buffer ensuring width is an integer multiple of character width. #+BEGIN_SRC emacs-lisp (when (image-type-available-p 'svg) (use-package svg-lib :straight (:type git :protocol ssh :host github :repo "rougier/svg-lib")) (use-package svg-tag-mode ;; :straight (:type git :protocol ssh :host github :repo "rougier/svg-tag-mode") :straight (:type git :protocol ssh :host github :repo "howardabrams/svg-tag-mode") :hook ((org-mode . svg-tag-mode) (emacs-lisp-mode . svg-tag-mode) (python-mode . svg-tag-mode)) :config (setq svg-tag-tags `(("NOTE:" . ((lambda (tag) (svg-tag-make "NOTE" :face 'org-done :inverse nil :margin 0 :radius 2)))) ;; ------------------------------------------------------------ ;; Two definitions of TODO That include the single line message ;; ------------------------------------------------------------ (,(rx word-start "TODO" word-end) . ((lambda (tag) (svg-tag-make "TODO" :face 'org-todo :radius 2 :inverse t :margin 0 :padding 0 :crop-right t)))) (,(rx word-start "TODO" (group (one-or-more space) (zero-or-more not-newline)) line-end) . ((lambda (tag) (svg-tag-make tag :face 'org-todo :radius 2 :margin 0 :padding 0 :crop-left t)))) ;; ------------------------------------------------------------ ;; Two definitions of tag combo, like :TAG:foobar: ;; ------------------------------------------------------------ ;; Where the first is inversed: ;; (,(rx (group ":" (one-or-more (any alpha "-")) ":") ;; (one-or-more (any alphanumeric "-" "_")) ":") . ;; ((lambda (tag) ;; (svg-tag-make tag :beg 1 :end -1 :inverse t :margin 0 :crop-right t)))) ;; And the second is not: ;; (,(rx ":" (one-or-more (any alpha "-")) ;; (group ":" (one-or-more (any alphanumeric "-" "_")) ":")) . ;; ((lambda (tag) ;; (svg-tag-make tag :beg 1 :end -1 :margin 0 :crop-left t)))) ;; ------------------------------------------------------------ ;; Org-mode :TAGS: shown as a box: ;; ------------------------------------------------------------ (,(rx (or line-start space) (group ":" (one-or-more (any alpha "-")) ":") (or space line-end)) . ((lambda (tag) (svg-tag-make tag :face 'org-drawer :beg 1 :end -1 :margin 0)))) ;; ------------------------------------------------------------ ;; Org-mode blocks #+BEGIN_SRC language ... #+END_SRC ;; ------------------------------------------------------------ (,(rx line-start (zero-or-more space) "#+BEGIN_SRC") . ((lambda (tag) (svg-tag-make "RUN" :face 'org-block-begin-line :inverse t :margin 0 :crop-right t)) (lambda () (interactive) (org-ctrl-c-ctrl-c)) "Run this block of code")) (,(rx line-start (zero-or-more space) "#+BEGIN_SRC" (1+ space) (group (one-or-more (any alpha "-"))) word-end) . ((lambda (tag) (svg-tag-make tag :face 'org-block-begin-line :margin 0 :crop-left t)) (lambda () (interactive) (org-ctrl-c-ctrl-c)) "Run this block of code")) (,(rx line-start (zero-or-more space) "#+END_SRC" word-end) . ((lambda (tag) (svg-tag-make "END" :face 'org-block-end-line :inverse t)))) ("^#\\+RESULTS:" . ((lambda (tag) (svg-tag-make "RESULTS" :face 'org-block-end-line :inverse t)))) (,(rx line-start (zero-or-more space) "#+ATTR" (zero-or-more (any alpha "_")) ":") . ((lambda (tag) (svg-tag-make "ATTR" :face 'org-block-begin-line :inverse t)))) ("^#\\+NAME:" . ((lambda (tag) (svg-tag-make "NAME" :face 'org-meta-line)))) ("^#\\+HEADER:" . ((lambda (tag) (svg-tag-make "HEADER" :face 'org-meta-line)))) ("^#\\+BEGIN_EXAMPLE" . ((lambda (tag) (svg-tag-make "CODE" :face 'org-block-begin-line)))) ("^#\\+END_EXAMPLE" . ((lambda (tag) (svg-tag-make "END" :face 'org-block-end-line)))) ("^#\\+begin_quote" . ((lambda (tag) (svg-tag-make "“" :face 'org-block-begin-line)))) ("^#\\+end_quote" . ((lambda (tag) (svg-tag-make "”" :face 'org-block-end-line)))) ;; ------------------------------------------------------------ ;; Things like #+OPTIONS: and property definitions ;; ------------------------------------------------------------ ("#\\+PROPERTY:" . ((lambda (tag) (svg-tag-make "PROPERTY" :face 'org-meta-line)))) ("#\\+OPTIONS:" . ((lambda (tag) (svg-tag-make "OPTIONS" :face 'org-meta-line)))))))) #+END_SRC What does do? Here are some examples: TODO Marks comments for tasks (this can be in source files too). NOTE: Highlights comments and other notes. :PROP:tag: are highlighted as two parts of the same tag And :TAG: with colons are highlighted, which include :PROPERTY: drawers. Org-specific #+PROPERTY: entries are highlighted. #+BEGIN_SRC emacs-lisp :tangle no (use-package notebook :straight (:type git :protocol ssh :host github :repo "rougier/notebook-mode") :after org :hook (org-mode . notebook-mode)) #+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 :tangle no (use-package org-padding :straight (:type git :protocol ssh :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. * Pasting I like the idea that I will paste HTML text from the clipboard and have it converted to org-formatted text: #+BEGIN_SRC emacs-lisp (defun ha-org-paste () (interactive) (if (eq system-type 'gnu/linux) (shell-command "xclip -t text/html -o | pandoc -r html -w org" t))) #+END_SRC * 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: Let's provide a name so that the file can be required: #+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