#+TITLE:  Capturing Notes with Org
#+AUTHOR: Howard X. Abrams
#+DATE:   2020-09-18

A literate programming file for configuring org for capturing notes.

#+begin_src emacs-lisp :exports none
  ;;; capturing-notes --- Configuring org for capturing notes. -*- lexical-binding: t; -*-
  ;;
  ;; © 2020-2023 Howard X. Abrams
  ;;   Licensed under a Creative Commons Attribution 4.0 International License.
  ;;   See http://creativecommons.org/licenses/by/4.0/
  ;;
  ;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
  ;; Maintainer: Howard X. Abrams
  ;; 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-capturing-notes.org
  ;;       And tangle the file to recreate this one.
  ;;
  ;;; Code:
#+end_src
* 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:
#+begin_src emacs-lisp
  (setq org-capture-default-template "cc")
#+end_src

Let's now define my templates.
* Templates
To make sure we can execute this code anytime, let's define the variable that will hold all the templates:
#+begin_src emacs-lisp
  (defvar org-capture-templates (list))
#+end_src

Some templates put the information /in front/ of other information (as opposed to the default of appending), so I define a helper function:

#+begin_src emacs-lisp
  (defun ha-first-header ()
    (goto-char (point-min))
    (search-forward-regexp "^\* ")
    (beginning-of-line 1)
    (point))
#+end_src
** General Notes
Capturing text into the =org-default-notes-file= is something I don't do much:

#+begin_src emacs-lisp
  (add-to-list 'org-capture-templates
               '("n" "Thought or Note"  entry
                 (file org-default-notes-file "General Notes")
                 "* %?\n\n  %i\n\n  See: %a" :empty-lines 1))

  (add-to-list 'org-capture-templates
               '("t" "Task"  entry
                 (file+olp org-default-notes-file "Tasks")
                 "** %?\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))
#+end_src
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 enter details under that task without switching to it:
#+begin_src emacs-lisp
  (add-to-list 'org-capture-templates
               '("c" "Currently clocked in task"))
#+end_src

The /default/ is just to type information to the current clocked-in task using ~c c~:
#+begin_src emacs-lisp
  (add-to-list 'org-capture-templates
               `("ci" "Item to Current Clocked Task" item
                 (clock)
                 "%?" :empty-lines 1))
#+end_src

We can select a /region/ and copy that using ~c r~:
#+begin_src emacs-lisp
  (add-to-list 'org-capture-templates
               `("cc" "Contents to Current Clocked Task" plain
                 (clock)
                 "%i" :immediate-finish t :empty-lines 1))
#+end_src

If we have copied anything into the clipboard, that information can be add to the current task using ~c k~:
#+begin_src emacs-lisp
  (add-to-list 'org-capture-templates
               `("ck" "Kill-ring to Current Clocked Task" plain
                 (clock)
                 "%c" :immediate-finish t :empty-lines 1))
#+end_src

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~:
#+begin_src emacs-lisp
  (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))
#+end_src

If I want a reference to the code, without any comments, I call ~c l~:
#+begin_src emacs-lisp
(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))
#+end_src

** Capture Helper Functions
To have a capture back-ref to a function and its code, we need to use this:
#+begin_src emacs-lisp
  (require 'which-func)
#+end_src

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:
#+begin_src emacs-lisp
  (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)))
#+end_src

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 defined above:
#+begin_src emacs-lisp
  (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))))
#+end_src

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.

#+begin_src emacs-lisp
  (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)))
#+end_src

** Code Capturing Functions
To call a capture for code, let's make two interactive functions, one copies the information, and the other pulls up a capturing window for comments:
#+begin_src emacs-lisp
  (defun ha-code-to-clock (&optional start end)
    "Send the selected code to the current clocked-in org-mode task."
    (interactive)
    (org-capture nil "cl"))

  (defun ha-code-comment-to-clock (&optional start end)
    "Send the selected code (with comments) to the current clocked-in org-mode task."
    (interactive)
    (org-capture nil "cf"))
#+end_src
And a less-disruptive keybinding:
#+begin_src emacs-lisp
  (ha-leader "C" '("capture code" . ha-code-to-clock))
  (ha-leader "o C" '("capture code" . ha-code-comment-to-clock))
#+end_src
* External Capturing
Using =emacsclient=, the operating system or other applications can trigger a call to capture content into Emacs. I started with the functions from [[https://macowners.club/posts/org-capture-from-everywhere-macos/][this essay]], which made a nice approach to opening and closing a frame:
#+begin_src emacs-lisp
  (defun start-capture-frame (&optional template-key)
    "Create a new frame and run `org-capture'."
    (interactive)
    (make-frame '((name . "capture")
                  (top . 300)
                  (left . 700)
                  (width . 80)
                  (height . 25)))
    (select-frame-by-name "capture")
    (delete-other-windows)
    (cl-letf (((symbol-function 'switch-to-buffer-other-window) 'switch-to-buffer))
      (org-capture nil template-key)))
#+end_src
When I call [[help:org-capture][org-capture]] in its own frame, I don’t want any other windows around, so we /rebind/ =org-capture= ’s call to switch the buffer to another window, to switch to the capture buffer.

Wouldn’t it be grand if when we finished capturing, the frame automatically closed:
#+begin_src emacs-lisp
  (defun org-capture-delete-frame ()
    "Hook for `org-capture-after-finalize-hook' to delete the frame."
    (message "Finished with the org-capture-after-finalize-hook")
    (when (equal "capture" (frame-parameter nil 'name))
      (delete-frame)))

  (add-hook 'org-capture-after-finalize-hook 'org-capture-delete-frame)
#+end_src

This external shell script calls the function to kick everything off from applications that aren’t Emacs:
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture
  /usr/local/bin/emacsclient -s work -n -e "(start-capture-frame)"
#+end_src

And for even quicker work, we can have special scripts tied to special keybindings:
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture-meeting :chmod 755
  /usr/local/bin/emacsclient -s work -n -e "(start-capture-frame \"sm\")"
#+end_src

** Pull MacOS-Specific Content
The [[https://gitlab.com/aimebertrand/org-mac-link][org-mac-link]] project makes it easy to tell Emacs to retrieve information from other apps, e.g. the URL of the opened tab in Firefox.
#+begin_src emacs-lisp
  (use-package org-mac-link
    :straight (:host gitlab :repo "aimebertrand/org-mac-link")
    :config
    (ha-leader "i" '("insert app info" . org-mac-link-get-link)))
#+end_src
We then call [[help:org-mac-link-get-link][org-mac-link-get-link]] to select the app, which then get the information from the app, and inserts it at point. While this is nice, it seems to be the wrong order. As we see something we like, say in Firefox, then we go into Emacs and hit ~SPC i~. What about an approach where we stay in Firefox. In other words, /send the information/, perhaps using [[help:org-capture][org-capture]].

** Push MacOS-Specific Content
I’m use [[https://github.com/deseven/icanhazshortcut][ICanHazShortcut]] to have a keybinding trigger a script (every simple). For instance:
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture-clock
  /usr/bin/osascript ~/bin/emacs-capture-clock.scr
#+end_src
But the following Applescript does the work:
#+begin_src applescript :sheband "#!/usr/bin/osascript" :tangle ~/bin/emacs-capture-clock.scr
  tell application "System Events" to set theApp to name of first application process whose frontmost is true

  -- Macintosh HD:Applications:iTerm.app:
  if "iTerm" is in theApp then
    set function to "ha-external-capture-code-to-org"
  else
    set function to "ha-external-capture-to-org"
  end if

  tell application "System Events" to keystroke "c" using command down

  set command to "/usr/local/bin/emacsclient -s work -e '(" & function & ")'"
  do shell script command

  -- Tell me it worked and what it did, since this runs in the background
  say "Capture complete"
#+end_src

Now we have some goodies on the clipboard, and the script uses =emacsclient= to call these functions to put those contents into clocked in task.
#+begin_src emacs-lisp
  (defun ha-external-capture-to-org ()
    "Calls `org-capture-string' on the contents of the Apple clipboard."
    (interactive)
    (org-capture-string "" "ck")
    (ignore-errors
      (delete-frame)))
#+end_src
Oh, and it this is from the Terminal program, let’s wrap it in a block:
#+begin_src emacs-lisp
  (defun ha-external-capture-code-to-org ()
    "Calls `org-capture-string' on the contents of the Apple clipboard."
    (interactive)
    (seq-let (type data) (ha-get-clipboard)
      (let* ((code (thread-last data
                                (s-replace "\r" "\n")
                                (s-trim)))
             (contents (format "#+begin_example\n%s\n#+end_example" code)))
        (message contents)
        (org-capture-string contents "cc")))
    (ignore-errors
      (delete-frame)))
#+end_src
#+begin_src conf :tangle ~/.config/iCanHazShortcut/config.ini
  [main]
  config version = 2
  shell = /bin/bash -l
  populate_menu_with_actions = yes
  show_hotkeys_in_menu = yes
  check_for_updates = yes
  start_on_login = yes
  show_icon_in_statusbar = yes
  set_workdir_with_cd = no
  window_x = -988
  window_y = 172
  window_width = 600
  window_height = 361
  shortcut_column_enabled = yes
  action_column_enabled = yes
  command_column_enabled = yes
  workdir_column_enabled = no
  shortcut_column_width = 80
  action_column_width = 160
  command_column_width = 173
  workdir_column_width = 100

  [shortcut1]
  shortcut = ⇧⌃⌥⌘E
  action = Personal Emacs
  command = open -a Emacs
  workdir =
  enabled = yes

  [shortcut2]
  shortcut = ⇧⌃⌥E
  action = Work Emacs
  command = FOR_WORK=yes open -a Emacs-Work
  workdir =
  enabled = yes

  [shortcut3]
  shortcut = ⇧⌃⌥⌘X
  action = Emacs Capture
  command = ~/bin/emacs-capture
  workdir =
  enabled = yes

  [shortcut4]
  shortcut = ⇧⌃⌥X
  action = Emacs Capture Clipboard
  command = ~/bin/emacs-capture-clock
  workdir =
  enabled = yes

  [shortcut5]
  shortcut = ⇧⌃⌥T
  action = iTerm
  command = open -a iTerm
  workdir =
  enabled = yes

  [shortcut6]
  shortcut = ⇧⌃⌥S
  action = Slack
  command = open -a Slack
  workdir =
  enabled = yes

  [shortcut7]
  shortcut = ⇧⌃⌥W
  action = Spotify
  command = open -a Spotify
  workdir =
  enabled = yes

  [shortcut8]
  shortcut = ⇧⌃⌥F
  action = Firefox
  command = open -a Firefox
  workdir =
  enabled = yes

  [shortcut9]
  shortcut = ⇧⌃⌥C
  action = Chome
  command = ~/bin/chrome.scr
  workdir =
  enabled = yes

  [shortcut10]
  shortcut = ⇧⌃⌥Q
  action = Keepass
  command = open -a KeepassXC
  workdir =
  enabled = yes

  [shortcut11]
  shortcut = ⇧⌃⌥Z
  action = Zoom
  command = open -a zoom.us
  workdir =
  enabled = yes

  [shortcut12]
  shortcut = ⌃F1
  action = Mute Zoom
  command = ~/bin/zoom-muter
  workdir =
  enabled = yes

  [shortcut13]
  shortcut = ⇧⌃⌥⌘M
  action = Capture Meeting
  command = ~/bin/emacs-capture-meeting
  workdir =
  enabled = yes

  [shortcut14]
  shortcut = ⇧⌃⌥⌘B
  action = Outlook
  command = open -a "Microsoft Outlook"
  workdir =
  enabled = yes

  [shortcut15]
  shortcut = ⇧⌃⌥⌘D
  action = Discord
  command = open -a Discord
  workdir =
  enabled = yes
#+end_src
Configure the *ICanHazShortcut* shortcuts to call these scripts, as in this screenshot:
[[file:screenshots/icanhazshortcuts.png]]
And here is the configuration file for that:
#+begin_src conf :tangle ~/.config/iCanHazShortcut/config.ini :mkdirp yes
[main]
config version = 2
shell = /bin/bash -l
populate_menu_with_actions = yes
show_hotkeys_in_menu = yes
check_for_updates = yes
start_on_login = yes
show_icon_in_statusbar = yes
set_workdir_with_cd = no
window_x = -988
window_y = 172
window_width = 600
window_height = 361
shortcut_column_enabled = yes
action_column_enabled = yes
command_column_enabled = yes
workdir_column_enabled = no
shortcut_column_width = 80
action_column_width = 160
command_column_width = 173
workdir_column_width = 100

[shortcut1]
shortcut = ⇧⌃⌥⌘E
action = Personal Emacs
command = open -a Emacs
workdir =
enabled = yes

[shortcut2]
shortcut = ⇧⌃⌥E
action = Work Emacs
command = open -a /usr/local/Cellar/emacs-plus@28/28.1/Emacs.app
workdir =
enabled = yes

[shortcut3]
shortcut = ⇧⌃⌥⌘X
action = Emacs Capture
command = ~/bin/emacs-capture
workdir =
enabled = yes

[shortcut4]
shortcut = ⇧⌃⌥X
action = Emacs Capture Clipboard
command = ~/bin/emacs-capture-clock
workdir =
enabled = yes

[shortcut5]
shortcut = ⇧⌃⌥T
action = iTerm
command = open -a iTerm
workdir =
enabled = yes

[shortcut6]
shortcut = ⇧⌃⌥S
action = Slack
command = open -a Slack
workdir =
enabled = yes

[shortcut7]
shortcut = ⇧⌃⌥W
action = Spotify
command = open -a Spotify
workdir =
enabled = yes

[shortcut8]
shortcut = ⇧⌃⌥F
action = Firefox
command = open -a Firefox
workdir =
enabled = yes

[shortcut9]
shortcut = ⇧⌃⌥C
action = Chome
command = ~/bin/chrome.scr
workdir =
enabled = yes

[shortcut10]
shortcut = ⇧⌃⌥Q
action = Keepass
command = open -a KeepassXC
workdir =
enabled = yes

[shortcut11]
shortcut = ⇧⌃⌥Z
action = Zoom
command = open -a zoom.us
workdir =
enabled = yes

[shortcut12]
shortcut = ⌃F1
action = Mute Zoom
command = ~/bin/zoom-muter
workdir =
enabled = yes
#+end_src
** Push Terminal Results
I use this =en= script to copy command line output into the Emacs-based engineering notebook to the current clocked-in task. I have two use cases.

First, at the end of a pipe sequence. For instance, this example is what I would type and see in the Terminal:
#+begin_example
$ openstack server list --format json | jq '.[1].Networks' | en -f js
{
  "cedev13": [
    "10.158.12.169"
  ]
}
#+end_example
But the output, along with being displayed, is also copied into my org file as:
#+begin_example
,#+begin_src js
{
  "cedev13": [
    "2.158.12.169"
  ]
}
,#+end_src
#+end_example

Second, if I want more information about the command, I can begin the command with =en=, as in:
#+begin_example
$ en -f js -n "The output from server list" openstack server list --format json
#+end_example
Which puts the following in my org file:
#+begin_example
The output from server list
,#+begin_src sh
openstack server list --format json
,#+end_src

,#+results:
,#+begin_src js
[
  {
    "ID": "36bf4825-fc5b-4414-8758-4f8523136215",
    "Name": "kolladev.cedev13.d501.eng.pdx.wd",
    "Status": "ACTIVE",
    "Networks": {
      "cedev13": [
        "2.158.12.143"
      ]
    },
    "Image": "fde6ba50-7b14-4821-96fe-f5b549adc6d3",
    "Flavor": "163"
  },
  {
  …
#+end_example

Here is the script I tangle to =~/bin/en=:
#+begin_src shell :shebang "#!/bin/bash" :tangle ~/bin/en
  #  Interface to my Engineering Notebook.
  #
  #  I use this script as the last pipe entry on the command line, to
  #  display the output, and also copy the output into the Emacs-based
  #  engineering notebook to the current clocked-in task.
  #
  #  Use the script as a 'runner' of a command as this script passes
  #  any extra command line options directly to the shell.

  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")' >/dev/null

  rm -f $FILE
#+end_src
* Keybindings
Along with kicking off the org-capture, I want to be able to clock-in and out:
#+begin_src emacs-lisp
  (with-eval-after-load 'ha-org
    (ha-local-leader :keymaps 'org-mode-map
      "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)))
#+end_src
* Technical Artifacts                                :noexport:
Let's provide a name so we can =require= this file.
#+begin_src emacs-lisp :exports none
  (provide 'ha-capturing-notes)
  ;;; ha-capturing-notes.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 for capturing notes.

#+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
