hamacs/ha-capturing-notes.org
Howard Abrams 465fb840c1 Add journal system as well as auto inserting
One big feature of the org-journal is the ability to auto generate
empty files, and I realized that I needed to kick it up a notch with
the auto-insert. Actually brought my old code from years ago, as it
still works.
2021-11-10 14:31:15 -08:00

12 KiB
Raw Blame History

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

CLOCK: [2021-05-25 Tue 13:35][2021-05-25 Tue 14:05] => 0:30

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:

    (general-evil-define-key 'normal org-mode-map
      :prefix "SPC m"
        "X" '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))