b84fbeb5d4
Minor tweaks to things that have bothered me.
688 lines
26 KiB
Org Mode
688 lines
26 KiB
Org Mode
#+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 <http://gitlab.com/howardabrams>
|
||
;; 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
|
||
<<variables>>
|
||
<<org-todo>>
|
||
<<ob-configuration>>
|
||
|
||
:config
|
||
<<ha-org-leader>>
|
||
<<visual-hook>>
|
||
<<text-files>>
|
||
<<org-font-lock>>
|
||
<<no-flycheck-in-org>>
|
||
<<ob-languages>>
|
||
<<ob-graphviz>>
|
||
<<ox-exporters>>
|
||
<<org-return-key>>
|
||
<<global-keybindings>>
|
||
<<org-keybindings>>)
|
||
#+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 "<f17>"
|
||
: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
|
||
** 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
|