hamacs/ha-org.org
Howard Abrams f4a69065fc Integrate Mail into Emacs
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.
2021-11-14 22:08:09 -08:00

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