Skip to content

Latest commit

 

History

History
407 lines (340 loc) · 18 KB

ha-org-word-processor.org

File metadata and controls

407 lines (340 loc) · 18 KB

Org As A Word Processor

A literate programming file for making Org file more readable.

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:

screenshots/org-as-word-processor.png

General Org Settings

Since I use ellipsis in my writing… to change how org renders a collapsed heading.

(use-package org
  :config
  (setq org-pretty-entities t
        org-hide-emphasis-markers t
        org-auto-align-tags nil
        org-tags-column 0
        org-ellipsis ""                ; …, ➡, ⚡, ▼, ↴, , ∞, ⬎, ⤷, ⤵
        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-agenda-breadcrumbs-separator ""))

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:

(setq org-list-demote-modify-bullet '(("*" . "+") ("+" . "-") ("-" . "-")
                                      ("1." . "a.") ("a." . "1.")))

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:

(use-package org
  :custom-face (org-indent ((t (:inherit fixed-pitch)))))

The following adds frame borders and window dividers to give space between window buffers. Do I need this when my Org files indent the text? Dunno, but it makes the view more pleasant.

(modify-all-frames-parameters
 '((right-divider-width . 2)
   (internal-border-width . 10)))

(dolist (face '(window-divider
                window-divider-first-pixel
                window-divider-last-pixel))
  (face-spec-reset-face face)
  (set-face-foreground face (face-attribute 'default :background)))

(set-face-background 'fringe (face-attribute 'default :background))

Hide Heading Stars

I’ve struggled with hiding the initial asterisks that denote a headline in Org.

(defun org-mode-hide-stars ()
  (font-lock-add-keywords
   nil
   '(("^\\*+ "
      (0 (put-text-property (match-beginning 0) (match-end 0) 'face
                            (list :foreground
                                  (face-attribute 'default :background))))))))

And hook this function to Org:

(use-package org
  :hook (org-mode . org-mode-hide-stars))

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 org-appear project seeks to fix this by showing the markup when the point is nearby:

(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)))

This makes the emphasis markers appear only in Evil’s insert mode.

Typographic Quotes

According to Artur Malabarba of 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 Orgzly and Orgro.

Stealing his function, and updating it a bit, so that “quotes” work to insert rounded quotation marks:

(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)))))))

Now we can take advantage of the abstraction for “double quotes”:

(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)

And something similar for single quotes:

(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)

Note: I still need to worry about how quotes affect 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.

(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)

Can we do the same with ellipses?

(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)

After reading 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 en dash. But if I type two dashes in a row—which identifies an emphasized clause—I can convert it directly to em dash. Continually typing a dash replaces that character with longer and longer dashes⸺

(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)

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.

(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))

This is now fine and ffantastic!

Org Beautify

The Org Beautify package, overrides my darker themes, and headlines should behave, so I manually set these:

(defun ha-word-processor-fonts ()
  "Configure `org-mode' fonts and faces."
  (interactive)
  (when window-system
    ;; First step is to make all Org header levels to use the variable
    ;; font, and be the same color as the default text:
    (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 'unspecified))

    (set-face-attribute 'org-block-begin-line nil :height 0.85)
    (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)))

We call this function when we start:

(ha-word-processor-fonts)

Org Modern

The org-modern project attempts to do a lot of what I was doing in this file.

(use-package org-modern
  :straight (:host github :repo "minad/org-modern")
  :after org
  :hook ( ; (add-hook 'org-mode-hook #'org-modern-mode)
         (org-agenda-finalize . org-modern-agenda))
  :custom
  (org-modern-table nil)
  :config
  (set-face-attribute 'org-modern-symbol nil :family "Iosevka")
  (global-org-modern-mode))

I like the smaller code blocks as well as the <2022-06-16 Thu> timestamps.

Checkboxes

According to an idea by Huy Trần, (and expanded by the org-modern project), we can prettify the list checkboxes. To make completed tasks more distinguishable, he changed the colors:

(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)

Padding

The 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:

(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))))

However, I’m just going to have to write a function to clean this.

(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)))

Now that is some complicated regular expressions.

Technical Artifacts

Note, according to this discussion (and especially 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:

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