hamacs/ha-capturing-notes.org
Howard Abrams 0d4b1e97c0 Fixed a bug with reloading my config file.
And a bit of clean up.
2022-02-25 14:57:43 -08:00

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)))