Cleaned the org capturing code

Using the org-mac-link and some custom functions, I can quickly get
information from external programs into my org files.
This commit is contained in:
Howard Abrams 2022-06-30 11:58:31 -07:00
parent 20ad7323ed
commit 403fd4a972
2 changed files with 179 additions and 51 deletions

View file

@ -28,7 +28,7 @@ Capturing (or collecting) notes from files, browsers, and meetings, is a great w
I even have external commands that kick-off the capturing process, and without a command this is what gets called: I even have external commands that kick-off the capturing process, and without a command this is what gets called:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq org-capture-default-template "c") (setq org-capture-default-template "cc")
#+end_src #+end_src
Let's now define my templates. Let's now define my templates.
@ -63,22 +63,21 @@ Capturing text into the =org-default-notes-file= is something I don't do much:
#+end_src #+end_src
Before we go too far, we should create a publishing file for the website announcement, and something for the journal. Before we go too far, we should create a publishing file for the website announcement, and something for the journal.
** Clock in Tasks ** 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 details under that task without switching to it: 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 #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
'("c" "Currently clocked in task")) '("c" "Currently clocked in task"))
#+end_src #+end_src
Let's put a bullet item under that task: The /default/ is just to type information to the current clocked-in task using ~c c~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
`("cc" "Item to Current Clocked Task" item `("cc" "Item to Current Clocked Task" item
(clock) (clock)
"%i%?" :empty-lines 1)) "%?" :empty-lines 1))
#+end_src #+end_src
We can select a /region/ and copy that using =c r=: We can select a /region/ and copy that using ~c r~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
`("cr" "Contents to Current Clocked Task" plain `("cr" "Contents to Current Clocked Task" plain
@ -86,7 +85,7 @@ We can select a /region/ and copy that using =c r=:
"%i" :immediate-finish t :empty-lines 1)) "%i" :immediate-finish t :empty-lines 1))
#+end_src #+end_src
If we have copied anything into the clipboard, that information can be add to the current task using =c k=: If we have copied anything into the clipboard, that information can be add to the current task using ~c k~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
`("ck" "Kill-ring to Current Clocked Task" plain `("ck" "Kill-ring to Current Clocked Task" plain
@ -94,7 +93,7 @@ If we have copied anything into the clipboard, that information can be add to th
"%c" :immediate-finish t :empty-lines 1)) "%c" :immediate-finish t :empty-lines 1))
#+end_src #+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=: 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 #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
`("cf" "Code Reference with Comments to Current Task" `("cf" "Code Reference with Comments to Current Task"
@ -104,7 +103,6 @@ Instead, if I am looking at some code, I can copy some code from a region, but u
#+end_src #+end_src
If I want a reference to the code, without any comments, I call ~c l~: If I want a reference to the code, without any comments, I call ~c l~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'org-capture-templates (add-to-list 'org-capture-templates
`("cl" "Link to Code Reference to Current Task" `("cl" "Link to Code Reference to Current Task"
@ -114,14 +112,12 @@ If I want a reference to the code, without any comments, I call ~c l~:
#+end_src #+end_src
** Capture Helper Functions ** Capture Helper Functions
To have a capture back-ref to a function and its code, we need to use this: To have a capture back-ref to a function and its code, we need to use this:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(require 'which-func) (require 'which-func)
#+end_src #+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: 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 #+begin_src emacs-lisp
(defun ha-org-capture-fileref-snippet (f type headers func-name) (defun ha-org-capture-fileref-snippet (f type headers func-name)
(let* ((code-snippet (let* ((code-snippet
@ -135,16 +131,14 @@ This helper function given a code /type/ and the /function/, analyzes the curren
(format "From ~%s~ (in [[file:%s::%s][%s]]):" (format "From ~%s~ (in [[file:%s::%s][%s]]):"
func-name file-name line-number func-name file-name line-number
file-base)))) file-base))))
(format " (format " %s
%s
,#+BEGIN_%s %s ,#+begin_%s %s
%s %s
,#+END_%s" initial-txt type headers code-snippet type))) ,#+end_%s" initial-txt type headers code-snippet type)))
#+end_src #+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 previously defined: 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 #+begin_src emacs-lisp
(defun ha-org-capture-code-snippet (f) (defun ha-org-capture-code-snippet (f)
"Given a file, F, this captures the currently selected text "Given a file, F, this captures the currently selected text
@ -153,7 +147,7 @@ For typical code references, we can get the label for Org's =SRC= block by takin
(with-current-buffer (find-buffer-visiting f) (with-current-buffer (find-buffer-visiting f)
(let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode))) (let ((org-src-mode (replace-regexp-in-string "-mode" "" (format "%s" major-mode)))
(func-name (which-function))) (func-name (which-function)))
(ha-org-capture-fileref-snippet f "SRC" org-src-mode func-name)))) (ha-org-capture-fileref-snippet f "src" org-src-mode func-name))))
#+end_src #+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. 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.
@ -163,52 +157,188 @@ Let's assume that we want to copy some text from a file, but it isn't source cod
"Given a file, F, this captures the currently selected text "Given a file, F, this captures the currently selected text
within an Org EXAMPLE block and a backlink to the file." within an Org EXAMPLE block and a backlink to the file."
(with-current-buffer (find-buffer-visiting f) (with-current-buffer (find-buffer-visiting f)
(ha-org-capture-fileref-snippet f "EXAMPLE" "" nil))) (ha-org-capture-fileref-snippet f "example" "" nil)))
#+end_src #+end_src
** Code Capturing Functions ** 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:
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:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-code-to-clock (&optional start end) (defun ha-code-to-clock (&optional start end)
"Send the currently selected code to the currently clocked-in org-mode task." "Send the selected code to the current clocked-in org-mode task."
(interactive) (interactive)
(org-capture nil "F")) (org-capture nil "cl"))
(defun ha-code-comment-to-clock (&optional start end) (defun ha-code-comment-to-clock (&optional start end)
"Send the currently selected code (with comments) to the "Send the selected code (with comments) to the current clocked-in org-mode task."
currently clocked-in org-mode task."
(interactive) (interactive)
(org-capture nil "f")) (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 ()
"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")
;; I am always in fullscreen mode for Emacs, so it doesn't always honor
;; the original settings specified above.
;; (when (...)
;; (toggle-frame-fullscreen))
(delete-other-windows)
(flet ((switch-to-buffer-other-window (buf) (switch-to-buffer buf)))
(org-capture)))
(defadvice org-capture-finalize
(after delete-capture-frame activate)
"Advise capture-finalize to close the frame."
(if (equal "capture" (frame-parameter nil 'name))
(delete-frame)))
(defadvice org-capture-destroy
(after delete-capture-frame activate)
"Advise capture-destroy to close the frame."
(if (equal "capture" (frame-parameter nil 'name))
(delete-frame)))
#+end_src
Which can have an external shell script:
#+begin_src sh :shebang "#!/bin/bash" :tangle ~/bin/emacs-capture
/usr/local/bin/emacsclient -s work -n -e "(start-capture-frame)"
#+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
Im 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 #+end_src
* External Capturing Now we have some goodies on the clipboard, and the script uses
If we put something on the clipboard using =xclip= or something, and then =emacsclient= to call these functions to put those contents into clocked in task.
perhaps =emacsclient= could call this function to put those contents into clocked in task.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-external-capture-to-org () (defun ha-external-capture-to-org ()
"Calls `org-capture-string' on the contents of the Apple clipboard." "Calls `org-capture-string' on the contents of the Apple clipboard."
(interactive) (interactive)
(org-capture-string (ha-org-clipboard) "ck") (org-capture-string "" "ck")
(ignore-errors (ignore-errors
(delete-frame))) (delete-frame)))
#+end_src #+end_src
Oh, and it this is from the Terminal program, lets 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)
(let ((contents (format "#+begin_example\n%s\n#+end_example" (ha-org-clipboard))))
(message contents)
(org-capture-string contents "cc"))
(ignore-errors
(delete-frame)))
#+end_src
The command that I can use to call it:
** 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.
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. 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 #+begin_src shell :shebang "#!/bin/bash" :tangle ~/bin/en
# Interface to my Engineering Notebook. # Interface to my Engineering Notebook.
# #
# Used as the last pipe entry on the command line, this displays the output, # I use this script as the last pipe entry on the command line, to
# and then copies the contents into the Emacs-based engineering notebook at the # display the output, and also copy the output into the Emacs-based
# currently clocked in task. # engineering notebook to the current clocked-in task.
# #
# And parameters to the script are added at the end of a list entry. # Use the script as a 'runner' of a command as this script passes
# any extra command line options directly to the shell.
function usage { function usage {
echo "$(basename $0) [ -t header-title ] [ -n notes ] [ -f format ] command arguments" echo "$(basename $0) [ -t header-title ] [ -n notes ] [ -f format ] [ command [ arguments ] ]"
exit 1 exit 1
} }
@ -256,21 +386,21 @@ The =en= script is used as the last pipe entry on the command line, this display
fi fi
if [ -n "$COMMAND" ] if [ -n "$COMMAND" ]
then then
echo "#+BEGIN_SRC sh" echo "#+begin_src sh"
echo "${COMMAND}" echo "${COMMAND}"
echo "#+END_SRC" echo "#+end_src"
echo echo
echo "#+RESULTS:" echo "#+results:"
fi fi
if [ -n "$FORMAT" ] if [ -n "$FORMAT" ]
then then
echo "#+BEGIN_SRC ${FORMAT}" echo "#+begin_src ${FORMAT}"
echo "${RESULTS}" echo "${RESULTS}"
echo "#+END_SRC" echo "#+end_src"
else else
echo "#+BEGIN_EXAMPLE" echo "#+begin_example"
echo "${RESULTS}" echo "${RESULTS}"
echo "#+END_EXAMPLE" echo "#+end_example"
fi fi
} }
@ -283,7 +413,7 @@ The =en= script is used as the last pipe entry on the command line, this display
# Now that the results are on the clipboard, the `c k` capture # Now that the results are on the clipboard, the `c k` capture
# sequence calls my "grab from the clipboard" capture template: # sequence calls my "grab from the clipboard" capture template:
emacsclient -s work -e '(org-capture-string "" "ck")' emacsclient -s work -e '(org-capture-string "" "ck")' >/dev/null
rm -f $FILE rm -f $FILE
#+end_src #+end_src
@ -308,9 +438,7 @@ Along with kicking off the org-capture, I want to be able to clock-in and out:
"c =" '("timestamp up" . org-clock-timestamps-up) "c =" '("timestamp up" . org-clock-timestamps-up)
"c -" '("timestamp down" . org-clock-timestamps-down))) "c -" '("timestamp down" . org-clock-timestamps-down)))
#+end_src #+end_src
* Technical Artifacts :noexport: * Technical Artifacts :noexport:
Let's provide a name so we can =require= this file. Let's provide a name so we can =require= this file.
#+begin_src emacs-lisp :exports none #+begin_src emacs-lisp :exports none
(provide 'ha-capturing-notes) (provide 'ha-capturing-notes)

View file

@ -438,8 +438,8 @@ Let's try this general "space" prefix by defining some top-level operations, inc
"SPC" '("M-x" . execute-extended-command) "SPC" '("M-x" . execute-extended-command)
"." '("repeat" . repeat) "." '("repeat" . repeat)
"!" 'shell-command "!" 'shell-command
"X" 'org-capture "X" '("org capture" . org-capture)
"L" 'org-store-link "L" '("store org link" . org-store-link)
"RET" 'bookmark-jump "RET" 'bookmark-jump
"a" '(:ignore t :which-key "apps") "a" '(:ignore t :which-key "apps")
"o" '(:ignore t :which-key "org/open") "o" '(:ignore t :which-key "org/open")