f4a69065fc
The mail system connects to my Gmail account with isync and notmuch, and allows me to interact with with it, like hey.com ... oh, and I can compose email messsage in org format. Pretty slick. Now, if I just used mail.
377 lines
15 KiB
Org Mode
377 lines
15 KiB
Org Mode
#+TITLE: General Org-Mode Configuration
|
|
#+AUTHOR: Howard X. Abrams
|
|
#+EMAIL: howard.abrams@gmail.com
|
|
#+DATE: 2020-09-18
|
|
#+FILETAGS: :emacs:
|
|
|
|
A literate programming file for configuring org-mode and those files.
|
|
# *Note:* After each change, /tangle it/ to the source destination with ~C-c C-v t~.
|
|
|
|
#+BEGIN_SRC emacs-lisp :exports none
|
|
;;
|
|
;; Copyright (C) 2020 Howard X. Abrams
|
|
;;
|
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
|
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
|
|
;; 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:
|
|
|
|
(use-package org
|
|
:straight (:type built-in) ; Problems with the 9.4.4 version
|
|
;; :straight (:type git :protocol ssh :repo
|
|
;; "git://git.sv.gnu.org/emacs/org-mode.git")
|
|
:mode ("\\.org" . org-mode) ; Addresses an odd warning
|
|
:init
|
|
#+END_SRC
|
|
* Initialization Section
|
|
Org is an important part of my Emacs world, and with a lot of customization (even though Spacemacs and Doom do a good job getting things started).
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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.
|
|
|
|
;; Speed Commands: If point is at the beginning of a headline or code
|
|
;; block in org-mode, single keys do fun things. See
|
|
;; org-speed-command-help for details (or hit the ? key at a headline).
|
|
org-use-speed-commands t
|
|
|
|
org-directory "~/personal"
|
|
org-default-notes-file "~/personal/general-notes.txt"
|
|
|
|
org-enforce-todo-dependencies t ; Can't close a task until subtasks are done
|
|
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
|
|
The following sections assume that org has been loaded, as this begin the configuration section:
|
|
#+BEGIN_SRC emacs-lisp
|
|
:config
|
|
#+END_SRC
|
|
I pretend that my org files are word processing files that wrap automatically:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-hook 'org-mode-hook #'visual-line-mode)
|
|
#+END_SRC
|
|
Files that end in =.txt= are still org files to me:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
|
|
#+END_SRC
|
|
Many of the files that I edit close some, but not all, of the headers using a file variable. Let's allow that to not insist that I need to approve that:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-to-list 'safe-local-variable-values '(org-content . 2))
|
|
#+END_SRC
|
|
** 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 only 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, just call `org-return' as usual."
|
|
(interactive)
|
|
(if (eolp)
|
|
(ha-org-special-return)
|
|
(org-return)))
|
|
#+END_SRC
|
|
|
|
And bind it to the Return key:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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= in order to open it.
|
|
- On a header? Let's create a new header (or maybe not).
|
|
- In a table? Let's create a new row.
|
|
- If we are really in a list, we can create a new item.
|
|
|
|
I really 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 only one blank line.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun org-really-in-item-p ()
|
|
"Similar to `org-in-item-p', however, 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 /current element/. However, this could be a line-level symbol, like paragraph or =list-item= only if 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:
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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:
|
|
|
|
#+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 quickly 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 I really like taking notes in a meeting, I don't always like the multiple windows I have opened, so I created this function that I can easily call to eliminate 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)) ; Only 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
|
|
Whenever I edit Emacs Lisp blocks from my tangle-able configuration files, I get a lot of superfluous warnings. Let's turn them off.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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
|
|
*** 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
|
|
Keybindings available to all file buffers:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(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:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(general-evil-define-key 'normal org-mode-map
|
|
:prefix "SPC m"
|
|
"e" '("exports" . org-export-dispatch)
|
|
"l" '("insert link" . org-insert-link)
|
|
"o" '("goto link" . ace-link-org)
|
|
|
|
"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 quickly jumping:
|
|
#+BEGIN_SRC emacs-lisp
|
|
(define-key org-mode-map (kbd "M-o") 'ace-link-org)
|
|
#+END_SRC
|
|
* Supporting Packages
|
|
At this point, we assume that the =use-package= for org is complete, so we can close it and allow other projects to be loaded:
|
|
#+BEGIN_SRC emacs-lisp
|
|
)
|
|
#+END_SRC
|
|
** Exporters
|
|
Need a few extra exporters:
|
|
#+BEGIN_SRC emacs-lisp :tangle no
|
|
(use-package ox-md
|
|
:after org
|
|
:straight nil
|
|
:config
|
|
(add-to-list 'org-export-backends 'md))
|
|
|
|
(use-package ox-confluence
|
|
:after org
|
|
:straight nil
|
|
:load-path "~/.doom.d/elisp")
|
|
#+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
|
|
** Writegood
|
|
|
|
The [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]] highlights passive and weasel words as typed. Shame it doesn't check for dangled prepositions.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package writegood-mode
|
|
:hook ((org-mode . writegood-mode)))
|
|
#+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)
|
|
;;; 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
|
|
#+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
|