#+TITLE: General Org-Mode Configuration #+AUTHOR: Howard X. Abrams #+DATE: 2020-09-18 #+FILETAGS: :emacs: A literate programming file for configuring org-mode and those files. #+BEGIN_SRC emacs-lisp :exports none ;;; ha --- Org configuration. -*- 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 18, 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.org ;; And tangle the file to recreate this one. ;; ;;; Code: #+END_SRC * Use Package Org is a /large/ complex beast with a gazillion settings, so I discuss these later in this document. #+BEGIN_SRC emacs-lisp (use-package org :mode ("\\.org" . org-mode) ; Addresses an odd warning :init <> <> <> :config <> <> <> <> <> <> <> <> <> <> <>) #+END_SRC One other helper routine is a =general= macro for org-mode files: #+NAME: ha-org-leader #+BEGIN_SRC emacs-lisp :tangle no (general-create-definer ha-org-leader :states '(normal visual motion) :keymaps 'org-mode-map :prefix "SPC m" :global-prefix "" :non-normal-prefix "S-SPC") #+END_SRC * Initialization Section Begin by initializing these org variables: #+NAME: variables #+BEGIN_SRC emacs-lisp :tangle no (setq org-return-follows-link t org-adapt-indentation nil ; Don't physically change files org-startup-indented t ; Visually show paragraphs indented org-list-indent-offset 2 org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0, ; but uses a trick to make it ; appear indented. org-imenu-depth 4 sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces. org-export-with-sub-superscripts nil org-directory "~/personal" org-default-notes-file "~/personal/general-notes.txt" org-enforce-todo-dependencies t ; Can't close a task without completed subtasks org-agenda-dim-blocked-tasks t org-log-done 'time org-completion-use-ido t org-outline-path-complete-in-steps nil org-src-tab-acts-natively t org-agenda-span 'day ; Default is 'week org-confirm-babel-evaluate nil org-src-fontify-natively t org-src-tab-acts-natively t) #+END_SRC * Configuration Section I pretend that my org files are word processing files that wrap automatically: #+NAME: visual-hook #+BEGIN_SRC emacs-lisp :tangle no (add-hook 'org-mode-hook #'visual-line-mode) #+END_SRC Files that end in =.txt= are still org files to me: #+NAME: text-files #+BEGIN_SRC emacs-lisp :tangle no (add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode)) (add-to-list 'safe-local-variable-values '(org-content . 2)) #+END_SRC *Note:* Org mode files with the =org-content= variable setting will collapse two levels headers. Let's allow that without the need to approve that. ** Better Return Hitting the ~Return~ key in an org file should format the following line based on context. For instance, at the end of a list, insert a new item. We begin with the interactive function that calls our code if we are at the end of the line. #+BEGIN_SRC emacs-lisp (defun ha-org-return () "If at the end of a line, do something special based on the information about the line by calling `ha-org-special-return', otherwise, `org-return' as usual." (interactive) (if (eolp) (ha-org-special-return) (org-return))) #+END_SRC And bind it to the Return key: #+NAME: org-return-key #+BEGIN_SRC emacs-lisp :tangle no (define-key org-mode-map (kbd "RET") #'ha-org-return) #+END_SRC What should we do if we are at the end of a line? - Given a prefix, call =org-return= as usual in an org file. - On a link, call =org-return= and open it. - On a header? Create a new header. - In a table? Create a new row. - In a list, create a new item. I should break this function into smaller bits ... #+BEGIN_SRC emacs-lisp (defun ha-org-special-return (&optional ignore) "Add new list item, heading or table row with RET. A double return on an empty element deletes it. Use a prefix arg to get regular RET." (interactive "P") (if ignore (org-return) (cond ;; Open links like usual ((eq 'link (car (org-element-context))) (org-return)) ((and (org-really-in-item-p) (not (bolp))) (if (org-element-property :contents-begin (org-line-element-context)) (progn (end-of-line) (org-insert-item)) (delete-region (line-beginning-position) (line-end-position)))) ;; ((org-at-heading-p) ;; (if (string= "" (org-element-property :title (org-element-context))) ;; (delete-region (line-beginning-position) (line-end-position)) ;; (org-insert-heading-after-current))) ((org-at-table-p) (if (-any? (lambda (x) (not (string= "" x))) (nth (- (org-table-current-dline) 1) (org-table-to-lisp))) (org-return) ;; empty row (beginning-of-line) (setf (buffer-substring (line-beginning-position) (line-end-position)) "") (org-return))) (t (org-return))))) #+END_SRC How do we know if we are in a list item? Lists end with two blank lines, so we need to make sure we are also not at the beginning of a line to avoid a loop where a new entry gets created with one blank line. #+BEGIN_SRC emacs-lisp (defun org-really-in-item-p () "Return item beginning position when in a plain list, nil otherwise. Unlike `org-in-item-p', this works around an issue where the point could actually be in some =code= words, but still be on an item element." (save-excursion (let ((location (org-element-property :contents-begin (org-line-element-context)))) (when location (goto-char location)) (org-in-item-p)))) #+END_SRC The org API allows getting the context associated with the /current element/. This could be a line-level symbol, like paragraph or =list-item=, but always when the point isn't /inside/ a bold or italics item. You know how HTML distinguishes between /block/ and /inline/ elements, org doesn't. So, let's make a function that makes that distinction: #+BEGIN_SRC emacs-lisp (defun org-line-element-context () "Return the symbol of the current block element, e.g. paragraph or list-item." (let ((context (org-element-context))) (while (member (car context) '(verbatim code bold italic underline)) (setq context (org-element-property :parent context))) context)) #+END_SRC ** Tasks I need to add a /blocked/ state: #+NAME: org-todo #+BEGIN_SRC emacs-lisp :tangle no (setq org-todo-keywords '((sequence "TODO(t)" "DOING(g)" "|" "DONE(d)") (sequence "BLOCKED(b)" "|" "CANCELLED(c)"))) #+END_SRC And I would like to have cute little icons for those states: #+NAME: org-font-lock #+BEGIN_SRC emacs-lisp (dolist (m '(org-mode org-journal-mode)) (font-lock-add-keywords m ; A bit silly but my headers are now `(("^\\*+ \\(TODO\\) " ; shorter, and that is nice canceled (1 (progn (compose-region (match-beginning 1) (match-end 1) "⚑") nil))) ("^\\*+ \\(DOING\\) " (1 (progn (compose-region (match-beginning 1) (match-end 1) "⚐") nil))) ("^\\*+ \\(CANCELED\\) " (1 (progn (compose-region (match-beginning 1) (match-end 1) "✘") nil))) ("^\\*+ \\(BLOCKED\\) " (1 (progn (compose-region (match-beginning 1) (match-end 1) "✋") nil))) ("^\\*+ \\(DONE\\) " (1 (progn (compose-region (match-beginning 1) (match-end 1) "✔") nil))) ;; Here is my approach for making the initial asterisks for listing items and ;; whatnot, appear as Unicode bullets ;; (without actually affecting the text ;; file or the behavior). ("^ +\\([-*]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))) #+END_SRC ** Meetings I've notice that while showing a screen while taking meeting notes, I don't always like showing other windows, so I created this function to remove distractions during a meeting. #+BEGIN_SRC emacs-lisp (defun meeting-notes () "Call this after creating an org-mode heading for where the notes for the meeting should be. After calling this function, call 'meeting-done' to reset the environment." (interactive) (outline-mark-subtree) ; Select org-mode section (narrow-to-region (region-beginning) (region-end)) ; Show that region (deactivate-mark) (delete-other-windows) ; remove other windows (text-scale-set 2) ; readable by others (fringe-mode 0) (message "When finished taking your notes, run meeting-done.")) #+END_SRC Of course, I need an 'undo' feature when the meeting is over… #+BEGIN_SRC emacs-lisp (defun meeting-done () "Attempt to 'undo' the effects of taking meeting notes." (interactive) (widen) ; Opposite of narrow-to-region (text-scale-set 0) ; Reset the font size increase (fringe-mode 1) (winner-undo)) ; Put the windows back in place #+END_SRC ** Searching Now that my paragraphs in an org file are on a single line, I need this less, but being able to use an /indexed search system/, like [[https://ss64.com/osx/mdfind.html][mdfind]] on Macos, or [[https://www.lesbonscomptes.com/recoll/][recoll]] on Linux, gives better results that line-oriented search systems, like =grep=. Let’s create operating-system functions the command line for searching: #+BEGIN_SRC emacs-lisp (defun ha-search-notes--macos (phrase path) "Return the indexed search system command on MACOS, mdfind. Including the parameters using the PHRASE on the PATH(s)." (let ((paths (if (listp path) (mapconcat (lambda (p) (concat "-onlyin " p)) path " ") (concat "-onlyin " path)))) (format "mdfind %s -interpret %s" paths phrase))) (defun ha-search-notes--linux (phrase path) "Return the indexed search system command on Linux, recoll. Including the parameters using the PHRASE on the PATH(s)." (format "recoll -t -a -b %s" phrase)) #+END_SRC This function calls these operating-system functions, but returns the matching files as a /single string/ (where each file is wrapped in single quotes, and all joined together, separated by spaces. This function also allows me to /not-match/ backup files and whatnot. #+BEGIN_SRC emacs-lisp (defun ha-search-notes--files (phrase path) "Return an escaped string of all files matching PHRASE. On a Mac, this search is limited by PATH" (let ((command (if (equal system-type 'darwin) (ha-search-notes--macos phrase path) (ha-search-notes--linux phrase path)))) (->> command (shell-command-to-list) ; Function from piper! (--remove (s-matches? "~$" it)) (--remove (s-matches? "#" it)) (--map (format "'%s'" it)) (s-join " ")))) #+END_SRC The =ha-search-notes= function prompts for the phrase to search, and then searches through the =org-directory= path to acquire the matching files. It then feeds that list to =grep= (and the [[help:grep][grep function]] in order to display a list of matches that I can jump to. #+BEGIN_SRC emacs-lisp (defun ha-search-notes (phrase &optional path) "Search files in PATH for PHRASE and display in a grep mode buffer." (interactive "sSearch notes for: ") (let* ((command (if (equal system-type 'darwin) "ggrep" "grep")) (regexp (string-replace " " "\\|" phrase)) (use-paths (if path path (list org-directory org-journal-dir))) (files (ha-search-notes--files phrase use-paths))) (grep (format "%s -ni -m 1 '%s' %s" command regexp files)))) #+END_SRC Eventually, I would like to change the output so that the title of the Org mode is displayed instead of the first match, but that is good enough. #+BEGIN_SRC emacs-lisp (ha-leader "f n" '("find notes" . ha-search-notes)) #+END_SRC ** Misc *** Babel Blocks I use [[https://orgmode.org/worg/org-contrib/babel/intro.html][org-babel]] (obviously) and don’t need confirmation before evaluating a block: #+NAME: ob-configuration #+BEGIN_SRC emacs-lisp :tangle no (setq org-confirm-babel-evaluate nil org-src-fontify-natively t org-src-tab-acts-natively t) #+END_SRC Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off. #+NAME: no-flycheck-in-org #+BEGIN_SRC emacs-lisp :tangle no (defun disable-flycheck-in-org-src-block () (setq-local flycheck-disabled-checkers '(emacs-lisp-checkdoc))) (add-hook 'org-src-mode-hook 'disable-flycheck-in-org-src-block) #+END_SRC And turn on ALL the languages: #+NAME: ob-languages #+BEGIN_SRC emacs-lisp :tangle no (org-babel-do-load-languages 'org-babel-load-languages '((shell . t) (js . t) (emacs-lisp . t) (clojure . t) (python . t) (ruby . t) (dot . t) (css . t) (plantuml . t))) #+END_SRC The [[https://graphviz.org/][graphviz project]] can be written in org blocks, and then rendered as an image: #+NAME: ob-graphviz #+BEGIN_SRC emacs-lisp :tangle no (add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot")) #+END_SRC For example: #+BEGIN_SRC dot :file support/ha-org-graphviz-example.png :exports file :results replace file digraph G { graph [bgcolor=transparent]; edge [color=white]; node[style=filled]; A -> B -> E; A -> D; A -> C; E -> F; E -> H D -> F; A -> H; E -> G; } #+END_SRC #+ATTR_ORG: :width 400px #+RESULTS: [[file:support/ha-org-graphviz-example.png]] *** Next Image When I create images or other artifacts that I consider /part/ of the org document, I want to have them based on the org file, but with a prepended number. Keeping track of what numbers are now free is difficult, so for a /default/ let's figure it out: #+BEGIN_SRC emacs-lisp (defun ha-org-next-image-number (&optional prefix) (when (null prefix) (if (null (buffer-file-name)) (setq prefix "cool-image") (setq prefix (file-name-base (buffer-file-name))))) (save-excursion (goto-char (point-min)) (let ((largest 0) (png-reg (rx (literal prefix) "-" (group (one-or-more digit)) (or ".png" ".svg")))) (while (re-search-forward png-reg nil t) (setq largest (max largest (string-to-number (match-string-no-properties 1))))) (format "%s-%02d" prefix (1+ largest))))) #+END_SRC *** In a PlantUML Block To make the snippets more context aware, this predicate #+BEGIN_SRC emacs-lisp (defun ha-org-nested-in-plantuml-block () "Predicate is true if point is inside a Plantuml Source code block in org-mode." (equal "plantuml" (plist-get (cadr (org-element-at-point)) :language))) #+END_SRC ** Keybindings Global keybindings available to all file buffers: #+NAME: global-keybindings #+BEGIN_SRC emacs-lisp :tangle no (ha-leader "o l" '("store link" . org-store-link) "o x" '("org capture" . org-capture) "o c" '("clock out" . org-clock-out)) #+END_SRC Bindings specific to org files: #+NAME: org-keybindings #+BEGIN_SRC emacs-lisp :tangle no (ha-org-leader "e" '("exports" . org-export-dispatch) "I" '("insert id" . org-id-get-create) "l" '("insert link" . org-insert-link) "N" '("store link" . org-store-link) "o" '("goto link" . ace-link-org) "P" '("set property" . org-set-property) "q" '("set tags" . org-set-tags-command) "t" '("todo" . org-todo) "T" '("list todos" . org-todo-list) "h" '("toggle heading" . org-toggle-heading) "i" '("toggle item" . org-toggle-item) "x" '("toggle checkbox" . org-toggle-checkbox) "." '("goto heading" . consult-org-heading) "/" '("agenda" . consult-org-agenda) "'" '("edit" . org-edit-special) "*" '("C-c *" . org-ctrl-c-star) "+" '("C-c -" . org-ctrl-c-minus) "d" '(:ignore t :which-key "dates") "d s" '("schedule" . org-schedule) "d d" '("deadline" . org-deadline) "d t" '("timestamp" . org-time-stamp) "d T" '("inactive time" . org-time-stamp-inactive) "b" '(:ignore t :which-key "tables") "b -" '("insert hline" . org-table-insert-hline) "b a" '("align" . org-table-align) "b b" '("blank field" . org-table-blank-field) "b c" '("create teable" . org-table-create-or-convert-from-region) "b e" '("edit field" . org-table-edit-field) "b f" '("edit formula" . org-table-edit-formulas) "b h" '("field info" . org-table-field-info) "b s" '("sort lines" . org-table-sort-lines) "b r" '("recalculate" . org-table-recalculate) "b d" '(:ignore t :which-key "delete") "b d c" '("delete column" . org-table-delete-column) "b d r" '("delete row" . org-table-kill-row) "b i" '(:ignore t :which-key "insert") "b i c" '("insert column" . org-table-insert-column) "b i h" '("insert hline" . org-table-insert-hline) "b i r" '("insert row" . org-table-insert-row) "b i H" '("insert hline ↓" . org-table-hline-and-move) "n" '(:ignore t :which-key "narrow") "n s" '("subtree" . org-narrow-to-subtree) "n b" '("block" . org-narrow-to-block) "n e" '("element" . org-narrow-to-element) "n w" '("widen" . widen)) #+END_SRC Oh, and we'll use [[https://github.com/abo-abo/ace-link][ace-link]] for jumping: #+BEGIN_SRC emacs-lisp (use-package ace-link :after org :config (define-key org-mode-map (kbd "s-o") 'ace-link-org)) #+END_SRC * Supporting Packages ** Exporters Limit the number of exporters to the ones that I would use: #+NAME: ox-exporters #+BEGIN_SRC emacs-lisp (setq org-export-backends '(ascii html icalendar md odt)) #+END_SRC I have a special version of tweaked [[file:elisp/ox-confluence.el][Confluence exporter]] for my org files: #+BEGIN_SRC emacs-lisp (use-package ox-confluence :after org :straight nil ; Located in my "elisp" directory :config (ha-org-leader "E" '("to confluence" . ox-export-to-confluence))) #+END_SRC And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode][graphviz-dot-mode]]: #+BEGIN_SRC emacs-lisp (use-package graphviz-dot-mode :mode "\\.dot\\'" :init (setq tab-width 4 graphviz-dot-indent-width 2 graphviz-dot-auto-indent-on-newline t graphviz-dot-auto-indent-on-braces t graphviz-dot-auto-indent-on-semi t)) #+END_SRC And we can install company support: #+BEGIN_SRC emacs-lisp :tangle no (use-package company-graphviz-dot) #+END_SRC ** Spell Checking Let's hook some spell-checking into org files, and actually all text files. We'll use [[https://www.emacswiki.org/emacs/FlySpell][flyspell]] mode to highlight the misspelled words, and use the venerable [[https://www.emacswiki.org/emacs/InteractiveSpell][ispell]] for correcting. #+BEGIN_SRC emacs-lisp (use-package flyspell :hook (text-mode . flyspell-mode) :bind ("M-S" . ha-fix-last-spelling) :init ;; Tell ispell.el that ’ can be part of a word. (setq ispell-local-dictionary-alist `((nil "[[:alpha:]]" "[^[:alpha:]]" "['\x2019]" nil ("-B") nil utf-8))) :config (defun ha-fix-last-spelling (count) "Jump to the last misspelled word, and correct it." (interactive "p") (save-excursion (evil-prev-flyspell-error count) (ispell-word))) (evil-define-key 'insert text-mode-map (kbd "M-s M-s") 'ha-fix-last-spelling) (ha-local-leader :keymaps 'text-mode-map "s" '(:ignore t :which-key "spellcheck") "s s" '("correct last misspell" . ha-fix-last-spelling) "s b" '("check buffer" . flyspell-buffer) "s c" '("correct word" . flyspell-auto-correct-word) "s p" '("previous misspell" . evil-prev-flyspell-error) "s n" '("next misspell" . evil-next-flyspell-error))) #+END_SRC Sure, the keys, ~[ s~ and ~] s~ can jump to misspelled words, and use ~M-$~ to correct them, but I'm getting used to these leaders. A real issue I often face is I can be typing along and notice a mistake after entering a more words. Since this happens in /insert/ mode, I have bound ~M-s~ (twice) to fixing this spelling mistake, and jumping back to where I am. If the spelling mistake is /obvious/, I use ~C-;~ to call =flyspell-auto-correct-word=. According to [[http://endlessparentheses.com/ispell-and-apostrophes.html][Artur Malabarba]], we can turn on rounded apostrophe's, like =‘= (left single quotation mark). The idea is to not send the quote to the sub-process: #+BEGIN_SRC emacs-lisp (defun endless/replace-apostrophe (args) "Don't send ’ to the subprocess." (cons (replace-regexp-in-string "’" "'" (car args)) (cdr args))) (advice-add #'ispell-send-string :filter-args #'endless/replace-apostrophe) (defun endless/replace-quote (args) "Convert ' back to ’ from the subprocess." (if (not (derived-mode-p 'org-mode)) args (cons (replace-regexp-in-string "'" "’" (car args)) (cdr args)))) (advice-add #'ispell-parse-output :filter-args #'endless/replace-quote) #+END_SRC The end result? No misspellings. Isn‘t this nice? Of course I need a thesaurus, and I'm installing [[https://github.com/SavchenkoValeriy/emacs-powerthesaurus][powerthesaurus]]: #+BEGIN_SRC emacs-lisp (use-package powerthesaurus :bind ("M-T" . powerthesaurus-lookup-dwim) :config (ha-local-leader :keymaps 'text-mode-map "s t" '("thesaurus" . powerthesaurus-lookup-dwim) "s s" '("synonyms" . powerthesaurus-lookup-synonyms-dwim) "s a" '("antonyms" . powerthesaurus-lookup-antonyms-dwim) "s r" '("related" . powerthesaurus-lookup-related-dwim) "s S" '("sentence" . powerthesaurus-lookup-sentences-dwim))) #+END_SRC The key-bindings, keystrokes, and key-connections work well with ~M-T~ (notice the Shift), but to jump to specifics, we use a leader. Since the /definitions/ do not work, so let's use abo-abo's [[https://github.com/abo-abo/define-word][define-word]] project: #+BEGIN_SRC emacs-lisp (use-package define-word :config (ha-local-leader :keymaps 'text-mode-map "s d" '("define this" . define-word-at-point) "s D" '("define word" . define-word))) #+END_SRC ** Focused Work :LOGBOOK: CLOCK: [2022-02-11 Fri 11:05]--[2022-02-11 Fri 11:21] => 0:16 :END: I've been working on my own [[http://www.howardism.org/Technical/Emacs/focused-work.html][approach to focused work]], #+BEGIN_SRC emacs-lisp (use-package async) (use-package ha-focus :straight (:type built-in) :config (ha-leader "o f" '("begin focus" . ha-focus-begin) "o F" '("break focus" . ha-focus-break))) #+END_SRC ** Grammar and Prose Linting Flagging cliches, weak phrasing and other poor grammar choices. *** Writegood The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] is effective at highlighting passive and weasel words, but isn’t integrated into =flycheck=: #+BEGIN_SRC emacs-lisp :tangle no (use-package writegood-mode :hook ((org-mode . writegood-mode))) #+END_SRC We install the =write-good= NPM: #+BEGIN_SRC shell npm install -g write-good #+END_SRC And check that the following works: #+BEGIN_SRC sh :results output write-good --text="So it is what it is." #+END_SRC Now, let’s connect it to flycheck: #+BEGIN_SRC emacs-lisp (use-package flycheck :config (flycheck-define-checker write-good "A checker for prose" :command ("write-good" "--parse" source-inplace) :standard-input nil :error-patterns ((warning line-start (file-name) ":" line ":" column ":" (message) line-end)) :modes (markdown-mode org-mode text-mode)) (add-to-list 'flycheck-checkers 'vale 'append)) #+END_SRC *** Proselint With overlapping goals to =write-good=, the [[https://github.com/amperser/proselint/][proselint]] project, once installed, can check for some English phrasings. I like =write-good= better, but I want this available for its level of /pedantic-ness/. #+BEGIN_SRC sh brew install proselint #+END_SRC Next, create a configuration file, =~/.config/proselint/config= file, to turn on/off checks: #+BEGIN_SRC js :tangle ~/.config/proselint/config.json :mkdirp yes { "checks": { "typography.diacritical_marks": false, "annotations.misc": false, "consistency.spacing": false } } #+END_SRC And tell [[https://www.flycheck.org/][flycheck]] to use this: #+BEGIN_SRC emacs-lisp (use-package flycheck :config (add-to-list 'flycheck-checkers 'proselint)) #+END_SRC ** Distraction-Free Writing *** Write-room For a complete focused, /distraction-free/ environment, for writing or concentrating, I'm using [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]: #+BEGIN_SRC emacs-lisp (use-package writeroom-mode :hook (writeroom-mode-disable . winner-undo) :config (ha-leader "t W" '("writeroom" . writeroom-mode)) (ha-leader :keymaps 'writeroom-mode-map "=" '("adjust width" . writeroom-adjust-width) "<" '("decrease width" . writeroom-decrease-width) ">" '("increase width" . writeroom-increase-width)) :bind (:map writeroom-mode-map ("C-M-<" . writeroom-decrease-width) ("C-M->" . writeroom-increase-width) ("C-M-=" . writeroom-adjust-width))) #+END_SRC *** Olivetti The [[https://github.com/rnkn/olivetti][olivetti project]] sets wide margins and centers the text. It isn’t better than Writeroom, however, it works well with Logos (below). #+BEGIN_SRC emacs-lisp (use-package olivetti :init (setq-default olivetti-body-width 100) :config (ha-leader "t O" '("olivetti" . olivetti-mode)) :bind (:map olivetti-mode-map ("C-M-<" . olivetti-shrink) ("C-M->" . olivetti-expand) ("C-M-=" . olivetti-set-width))) #+END_SRC *** Logos Trying out [[https://protesilaos.com/][Protesilaos Stavrou]]’s [[https://protesilaos.com/emacs/logos][logos project]] as a replacement for [[https://github.com/joostkremers/writeroom-mode][Writeroom-mode]]: #+BEGIN_SRC emacs-lisp (use-package logos :straight (:type git :protocol ssh :host gitlab :repo "protesilaos/logos") :init (setq logos-outlines-are-pages t logos-outline-regexp-alist `((emacs-lisp-mode . "^;;;+ ") (org-mode . "^\\*+ +") (t . ,(or outline-regexp logos--page-delimiter)))) ;; These apply when `logos-focus-mode' is enabled. Their value is ;; buffer-local. (setq-default logos-hide-mode-line t logos-scroll-lock nil logos-indicate-buffer-boundaries nil logos-buffer-read-only nil logos-olivetti t) :config (ha-leader "t L" '("logos" . logos-focus-mode)) (let ((map global-map)) (define-key global-map [remap narrow-to-region] #'logos-narrow-dwim) (evil-define-key 'normal map (kbd "g [") 'logos-backward-page-dwim) (evil-define-key 'normal map (kbd "g ]") 'logos-forward-page-dwim))) #+END_SRC * Technical Artifacts :noexport: Let's provide a name, to allow =require= to work: #+BEGIN_SRC emacs-lisp :exports none (provide 'ha-org) ;;; ha-org.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 configuring org-mode and those files. #+PROPERTY: header-args:sh :tangle no #+PROPERTY: header-args:emacs-lisp :tangle yes :noweb yes #+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes #+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