Do I really need the copyright symbol? I love how the proselint insists that I use the unicode character (which unicoding all the files sounds great to me). What could go wrong there? :-D
12 KiB
Capturing Notes with Org
A literate programming file for configuring org for capturing notes.
Introduction
Capturing (or collecting) notes from files, browsers, and meetings, is a great way to get organized.
I even have external commands that kick-off the capturing process, and without a command this is what gets called:
(setq org-capture-default-template "c")
Let's now define my templates.
Templates
Just make sure we can execute this code anytime, let's just define the variable that will hold all the templates:
(defvar org-capture-templates (list))
Some templates put the information in front of other information (as opposed to the default of appending), so I define a helper function:
(defun ha-first-header ()
(goto-char (point-min))
(search-forward-regexp "^\* ")
(beginning-of-line 1)
(point))
General Notes
Capturing text into the org-default-notes-file
is something I don't do much:
(add-to-list 'org-capture-templates
'("n" "Thought or Note" entry
(file org-default-notes-file)
"* %?\n\n %i\n\n See: %a" :empty-lines 1))
(add-to-list 'org-capture-templates
'("w" "Website Announcement" entry
(file+function "~/website/index.org" ha-first-header)
(file "~/.spacemacs.d/templates/website-announcement.org")
:empty-lines 1))
Before we go too far, we should create a publishing file for the website announcement, and something for the journal.
Clock in Tasks
Org has one task at a time that can be clocked in keeping a timer. I use that as a destination for collecting notes. For instance, capturing with a c
allows me to just enter stuff in under that task without switching to it:
(add-to-list 'org-capture-templates
'("c" "Currently clocked in task"))
Let's put a bullet item under that task:
(add-to-list 'org-capture-templates
`("cc" "Item to Current Clocked Task" item
(clock)
"%i%?" :empty-lines 1))
We can select a region and copy that using c r
:
(add-to-list 'org-capture-templates
`("cr" "Contents to Current Clocked Task" plain
(clock)
"%i" :immediate-finish t :empty-lines 1))
If we have copied anything into the clipboard, that information can be add to the current task using c k
:
(add-to-list 'org-capture-templates
`("ck" "Kill-ring to Current Clocked Task" plain
(clock)
"%c" :immediate-finish t :empty-lines 1))
Instead, if I am looking at some code, I can copy some code from a region, but use a helper function to create a link to the original source code using c f
:
(add-to-list 'org-capture-templates
`("cf" "Code Reference with Comments to Current Task"
plain (clock)
"%(ha-org-capture-code-snippet \"%F\")\n\n %?"
:empty-lines 1))
If I want a reference to the code, without any comments, I call c l
:
(add-to-list 'org-capture-templates
`("cl" "Link to Code Reference to Current Task"
plain (clock)
"%(ha-org-capture-code-snippet \"%F\")"
:empty-lines 1 :immediate-finish t))
Capture Helper Functions
In order to have a capture back-ref to a function and its code, we need to use this:
(require 'which-func)
This helper function given a code type and the function, analyzes the current buffer in order to collects data about the source code file. It then creates a nice-looking template:
(defun ha-org-capture-fileref-snippet (f type headers func-name)
(let* ((code-snippet
(buffer-substring-no-properties (mark) (- (point) 1)))
(file-name (buffer-file-name))
(file-base (file-name-nondirectory file-name))
(line-number (line-number-at-pos (region-beginning)))
(initial-txt (if (null func-name)
(format "From [[file:%s::%s][%s]]:"
file-name line-number file-base)
(format "From ~%s~ (in [[file:%s::%s][%s]]):"
func-name file-name line-number
file-base))))
(format "
%s
#+BEGIN_%s %s
%s
#+END_%s" initial-txt type headers code-snippet type)))
For typical code references, we can get the label for Org's SRC
block by taking the major-mode
and removing the -mode
part. We can then call the formatter we previously defined:
(defun ha-org-capture-code-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org SRC block with a language based on the current mode
and a backlink to the function and the file."
(with-current-buffer (find-buffer-visiting f)
(let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
(func-name (which-function)))
(ha-org-capture-fileref-snippet f "SRC" org-src-mode func-name))))
Let's assume that we want to copy some text from a file, but it isn't source code, then this function makes an EXAMPLE
of it.
(defun ha-org-capture-clip-snippet (f)
"Given a file, F, this captures the currently selected text
within an Org EXAMPLE block and a backlink to the file."
(with-current-buffer (find-buffer-visiting f)
(ha-org-capture-fileref-snippet f "EXAMPLE" "" nil)))
Code Capturing Functions
In order to easily call a capture for code, let's make two interactive functions, one just copies the stuff, and the other pulls up a capturing window for comments:
(defun ha-code-to-clock (&optional start end)
"Send the currently selected code to the currently clocked-in org-mode task."
(interactive)
(org-capture nil "F"))
(defun ha-code-comment-to-clock (&optional start end)
"Send the currently selected code (with comments) to the
currently clocked-in org-mode task."
(interactive)
(org-capture nil "f"))
External Interface
Emacs Server Control
Sure the Emacs application will almost always have the server-start
going, however, I need to control it just a bit (because I often have two instances running on some of my machines). What defines the Emacs instance for work changes … often:
(defun ha-emacs-for-work? ()
"Return non-nil when the Emacs application's location matches as one for work.
Currently, this is the `emacs-plus' app that I have built with
the native-comp model, but I reserve the right to change this."
(->> Info-default-directory-list
(first)
(s-split "/")
(--filter (s-starts-with? "emacs-plus" it))
(first)))
(if (ha-emacs-for-work?)
(setq server-name "work")
(setq server-name "personal"))
(server-start)
External Capturing
If we put something on the clipboard using xclip
or something, and then
perhaps emacsclient
could call this function to put those contents into clocked in task.
(defun ha-external-capture-to-org ()
"Calls `org-capture-string' on the contents of the Apple clipboard."
(interactive)
(org-capture-string (ha-org-clipboard) "ck")
(ignore-errors
(delete-frame)))
The en
script is used as the last pipe entry on the command line, this displays the output, and then copies the contents into the Emacs-based engineering notebook at the currently clocked in task.
# Interface to my Engineering Notebook.
#
# Used as the last pipe entry on the command line, this displays the output,
# and then copies the contents into the Emacs-based engineering notebook at the
# currently clocked in task.
#
# And parameters to the script are added at the end of a list entry.
function usage {
echo "$(basename $0) [ -t header-title ] [ -n notes ] [ -f format ] command arguments"
exit 1
}
while getopts "t:n:f:" o
do case "$o" in
t) TITLE="$OPTARG";;
n) NOTE="$OPTARG";;
f) FORMAT="$OPTARG";;
[?]) usage;;
esac
done
shift $(expr $OPTIND - 1)
COMMAND=$*
FILE=$(mktemp)
function process_output {
cat -v $1 | sed 's/\^\[\[[0-9][0-9]*\(;[0-9][0-9]*\)*m//g'
}
# The script can either take a command specified as arguments (in
# which case, it will run that), or it will assume all data is coming
# from standard in...
if [ -z "$COMMAND" ]
then
# All data should be coming from standard in, so capture it:
tee $FILE
else
# Otherwise, we need to run the command:
${COMMAND} | tee $FILE
fi
# Either way, let's process the results stored in the file:
RESULTS=$(process_output $FILE)
function output {
if [ -n "$TITLE" ]
then
echo "*** ${TITLE}"
fi
if [ -n "$NOTE" ]
then
echo "${NOTE}"
fi
if [ -n "$COMMAND" ]
then
echo "#+BEGIN_SRC sh"
echo "${COMMAND}"
echo "#+END_SRC"
echo
echo "#+RESULTS:"
fi
if [ -n "$FORMAT" ]
then
echo "#+BEGIN_SRC ${FORMAT}"
echo "${RESULTS}"
echo "#+END_SRC"
else
echo "#+BEGIN_EXAMPLE"
echo "${RESULTS}"
echo "#+END_EXAMPLE"
fi
}
if which pbcopy 2>&1 >/dev/null
then
output | pbcopy
else
output | xclip
fi
# Now that the results are on the clipboard, the `c k` capture
# sequence calls my "grab from the clipboard" capture template:
emacsclient -s work -e '(org-capture-string "" "ck")'
rm -f $FILE
Keybindings
Along with kicking off the org-capture, I want to be able to clock-in and out:
(with-eval-after-load 'ha-org
(ha-org-leader
"X" '("org capture" . org-capture)
"c" '(:ignore t :which-key "clocks")
"c i" '("clock in" . org-clock-in)
"c l" '("clock in last" . org-clock-in-last)
"c o" '("clock out" . org-clock-out)
"c c" '("cancel" . org-clock-cancel)
"c d" '("mark default task" . org-clock-mark-default-task)
"c e" '("modify effort" . org-clock-modify-effort-estimate)
"c E" '("set effort" . org-set-effort)
"c g" '("goto clock" . org-clock-goto)
"c r" '("resolve clocks" . org-resolve-clocks)
"c R" '("clock report" . org-clock-report)
"c t" '("eval range" . org-evaluate-time-range)
"c =" '("timestamp up" . org-clock-timestamps-up)
"c -" '("timestamp down" . org-clock-timestamps-down)))