‹ . local author .

: notes on place, literature, history, and method :

« Older posts
Page 6
Newer posts »

ZK Video Scripts

DONE Getting Started - Hydra and Inbox

  • hydra
    • entry point, bound to “C-z”
  • entry notes
    • inbox
  • functions
    • go-to functions
    • note count
    • nested hydra for ebib
  • create new note
  • find-file

    • when I am reading through a note and I have a sense that there is something worth linking to, I want to find that target note without a whole lot fuss
      • without breaking too far out of the task at hand
    • here I’m reading a note and see some business about volcanoes
      • and I know I’ve got some notes somewhere that deal with volcanoes, but I can’t exactly remember where or what they’re called so…
    • the first thing i reach for zk-find-file, a pretty general tool
      • searches by titles
    • “volcano”, leads to notes I don’t really want
    • so, i use:
  • find-file-by-full-text-search

    • shows notes that have the search string in the body of the note
    • and here are a bunch of notes – and maybe I know by the title that this one is what I want to add
  • you might be thinking, find-file is how you open a note

    • but you said you want to add a link to a note
    • so what are you going to do now?
  • well – I could remember the title of the note I want, then call zk-insert-link

  • but that’s some extract business that I don’t want to deal with, especially when I’ve already found the note with one function — I don’t want to have to find it again with another function

    • this is where Embark comes in
      • if you’re not familiar with embark:
        • it’s a package that allows you to perform alternative actions on the item at point, even within the context of another action
        • so in the middle of find file, if I want to do something different to a file, embark has a whole list of actions that you can do on files that it makes accessible through the command ’embark-act'
        • it offers a sort of submenu of commands
        • zk adds its own commands to this submenu
        • embark is sort of like a right click feature
        • in the context of a file, zk allows you to insert a link or copy a link
    • in this case, I want to insert a link
      • so I do embark-act (C-,) and then here are the list of possible actions
        • the quick key for insert is i
        • so I can just do embark-act i, and there it goes
    • do Volney
      • find file shows one i want to insert
      • be more thorough, look for more files
        • here’s one: but the point isn’t where I want
        • so instead of doing embark act i, I do embark-act k, which copies the link and title
        • and I can yank it into the buffer where I want it
  • so that’s it

    • quick, on the fly finding and insert links
  • I’ll show more with embark in a separate video, because there is much more

  • but next time we’ll look at even richer searching capabilities, using grep, consult, and zk-index

  • {2,1,d,2} 202202200949
  • in the last video showed how I use find-file to quickly find and insert links
  • this one shows how I do more in-depth searching, using zk-search and related functions and then also the package zk-index
    • if you are using zk and not zk-index, then you’re missing out on an interface that is key to my own work, which makes me happy, because it justifies the work I put into making it
    • so I’ll showcase how it’s useful to me, specifically in the context of searching
  • So I showed zk-find-file-by-full-title, which is a function I use most often when I’m in the middle of something and I just want to see IF I have notes containing a specific word or phrase; I just want to know, does my archive contain notes that mention this thing, whatever it is
    • periphery
    • peripher[y|ie|al]
    • If I have a lot, the minibuffer isn’t a great place to browse through those results
    • so we’re in business; but I want to see what those notes contain, to see if they’re relevant
  • so, first thing is zk-search
    • which is an interactive command that calls whatever function the variable zk-search-function is set to
    • zk-grep
      • by default that is the function zk-grep, which is a wrapper around rgrep, that searches for a regular expression in your zk-directory, and shows the results in a grep buffer
      • since I’ve already searched for what we want in zk-find-file-by-full-title we can use M-p to go back in the history, since all the search functions use the same history variable, zk-search-history
      • so if you haven’t changed the default setting, and call zk-search
        • but you can also call zk-grep as a command
        • and I’ll show why this is useful in a bit
      • show wgrep — for misspellings for example; or a tag that you want to change uniformly across all notes; or a citekey
    • zk-consult-grep
      • if you use Consult, you can load the package zk-consult and set the zk-search-function to zk-consult-grep
        • then you get a nice interface to grep results
        • if you use embark, you can emb[ark-export to a normal grep buffer
      • two different ways to get the same place, depending on your preferences
      • zk-consult-grep can also be used as a command, so you can just call it directly
    • I use both - sometimes I know I want a grep buffer, something that will stick around, that I can browse through, so I just go straight for zk-grep
    • sometimes I want to get where I’m going, and call zk-consult-grep for the quick minibuffer access
    • I have both easily accessible in my hydra
      • be aware: consult-grep has it’s own history
    • the idea behind zk is to provide low level functions that stay out of your way, that can be built on to facilitate what works for you
  • zk-xref
  • with that in mind, if you want to use a different search facility, other than grep, you can
    • for example, built in xref
    • zk-xref command is in zk-extras
    • since zk-search-function can be set to any function that takes a string, you can set it to zk-xref and off you go
  • tags work the same way
    • the interface prompts you with all the tags in your files
      • zk-tag-search-function
      • zk-tag-search
      • zk-consult-grep-tag-search
  • ZK-Index
    • one limitation of grep is that there is no boolean AND operator
    • search
    • sort - with large results (nature)
      • size
      • modified
      • created
    • I’ll do a separate video on ZK-Index and its companion, ZK-Desktop

ZK-Index and ZK-Desktop

ZK-Index

  • searching
    • zk-index-search
    • zk-index-focus
  • zk and embark export to new index
  • link-hint
    • link-hint-aw-select
    • link-hint-preview
  • popper

ZK-Desktop

  • send to desktop

    • from in buffer
    • from minibuffer
    • from index
  • multiple desktops

    • zk-index-desktop-select
  • any major mode

    • set zk-index-desktop-major-mode
    • org-mode
    • outline-mode
  • actions

    • type like normal
    • delete notes
    • copy and paste region
    • kill individual
    • rearranging notes
  • link-hint

    • link-hint-open
    • link-hint-aw-select
    • link-hint-preview
  • in buffer
  • in zk-index
  • in zk-desktop
  • custom avy actions
  • also in zk-index
  • and zk-desktop

ZK and Embark

;; from https://karthinks.com/software/fifteen-ways-to-use-embark/
(eval-when-compile
  (defmacro embark-aw-select (fn)
    `(defun ,(intern (concat "embark-aw-select-" (symbol-name fn))) ()
       (interactive)
       (with-demoted-errors "%s"
         (aw-switch-to-window (aw-select nil))
         (call-interactively (symbol-function ',fn))))))

(with-eval-after-load "embark"
  (define-key embark-file-map (kbd "o") (embark-aw-select find-file))
  (define-key embark-buffer-map (kbd "o") (embark-aw-select switch-to-buffer))
  (define-key embark-bookmark-map (kbd "o") (embark-aw-select bookmark-jump)))

ZK-Citar

Integration with new Citar 1.0 API

  • set zk-citar-citekey-regexp
  • access notes via citar dialog
  • create notes via citar dialog
  • access via embark on citekey

ZK-Luhmann

Expansion, Customizations

This post shows how to set up Emacs popup frames in macOS, allowing quick access to Emacs functions from anywhere on your system.

——————————————————————————————

This post shows how to set up Emacs popup frames in macOS, allowing quick access to Emacs functions from anywhere on your system.

——————————————————————————————

As much as I would like to live in Emacs full time, I’m not quite there. But a recent post by Protesilaos Stavrou (aka Prot) is helping me bridge the gap.

Prot has written a short but powerful bit of code that allows you to access any piece of Emacs goodness — like org-capture — from outside Emacs with just one keystroke. The only requirement is that you are running in server-mode or using the Emacs daemon.

Whether you’re in a web browser, a PDF reader, or just staring at an empty desktop, your favorite Emacs command can be right at your fingertips. See Prot’s video for a thorough demonstration.

Now, I can quickly open a PDF using citar or copy a password from my pass-store. See below for some other useful popups — org-agenda view, mu4e inbox, and a quick translation interface.

Setting Up Emacs Popup Frames in MacOS

Since Prot wrote the code for use in Linux, it didn’t immediately work in MacOS. It turns out, the only necessary change was to add a window-system frame parameter to the popup frame. (See code below).

But while I was at it, I made a few additions: 1) a title frame parameter, which allows me to configure the popup frame size and placement through my tiling window manager, yabai; and 2) an optional argument that runs delete-frame after the main command runs. This makes it possible to call commands that are minibuffer-centric — in my case, that’s citar-open-file and password-store-copy.

Here’s the main code:

(defun popup-frame-delete (&rest _)
  "Kill selected frame if it has parameter `popup-frame'."
  (when (frame-parameter nil 'popup-frame))
  (delete-frame))

(defmacro popup-frame-define (command title &optional delete-frame)
  "Define interactive function to call COMMAND in frame with TITLE."
  `(defun ,(intern (format "popup-frame-%s" command)) ()
     (interactive)
     (let* ((display-buffer-alist '(("")
                                    (display-buffer-full-frame))))
       (frame (make-frame
               '((title . ,title)
                 (window-system . ns)
                 (popup-frame . t)))))
     (select-frame frame)
     (switch-to-buffer " popup-frame-hidden-buffer")
     (condition-case nil
         (progn
           (call-interactively ',command)
           (delete-other-windows))
       (error (delete-frame frame)))
     (when ,delete-frame
       (sit-for 0.2)
       (delete-frame frame))))

(use-package server
  :defer 1
  :config
  (unless (server-running-p)
    (server-start)))

And here are the macro calls that define commands:

(popup-frame-define org-capture "capture-popup")
(add-hook 'org-capture-after-finalize-hook #'popup-frame-delete)

(popup-frame-define password-store-copy "minimal-popup" 'delete-frame)

(popup-frame-define citar-open-files "minimal-popup" 'delete-frame)

Triggering Popups with Keyboard Shortcuts

To quickly bring up these popup frames with keyboard shortcuts, I use skhd, a hotkey daemon for macOS. The following configuration goes in the skhd config file:

cmd + ctrl - c : emacsclient -e '(popup-frame-org-capture)' || yabai -m window –focus recent
cmd + ctrl - o : emacsclient -e '(popup-frame-citar-open-files)' || yabai -m window –focus recent
cmd + ctrl - p : emacsclient -e '(popup-frame-password-store-copy)' || yabai -m window –focus recent

Managing Windows with yabai

Finally, I have configured my tiling window manager, yabai, to give the popup frames the desired size and placement.

First, the following goes in the yabai config file:

yabai -m signal –add event=window_created app="Emacs" action="~/.yabai-emacs-window-handler.sh"

This will run a bash script (located at ~/.yabai-emacs-window-handler.sh) every time a new Emacs frame is created. The script tells yabai how to move and resize the new frame, depending on the frame’s title. Here is that script:

#!/bin/bash

data=$(yabai -m query –windows –window $YABAI_WINDOW_ID)

title=$(echo $data | jq .title)
display=$(echo $data | jq .display)

if [[ $title =~ "capture-popup" && $display == 1 ]]; then
yabai -m window $YABAI_WINDOW_ID –toggle float –move abs:430:230
yabai -m window $YABAI_WINDOW_ID –resize abs:655:300
yabai -m window $YABAI_WINDOW_ID –focus
elif
[[ $title =~ "minimal-popup" && $display == 1 ]]; then
yabai -m window $YABAI_WINDOW_ID –toggle float –move abs:430:230
yabai -m window $YABAI_WINDOW_ID –resize abs:655:200
yabai -m window $YABAI_WINDOW_ID –focus
else
false
fi

Additional Custom Commands

In some cases, an Emacs command will work nicely in a popup frame right out of the box. A good case in point is org-capture. In other cases, I have needed to write my own custom commands. Here a few:

Google Translate Interface

(defun my/translate ()
  (interactive)
  (let ((choice
         (completing-read "Select: " '("EN->LT" "LT->EN"))))
    (cond ((string= choice "EN->LT")
           (google-translate-query-translate-reverse))
          ((string= choice "LT->EN")
           (google-translate-query-translate)))))

(popup-frame-define my/translate "minimal-popup")

Unread Emails in mu4e

(defun my/mu4e-unread ()
  (interactive)
  (mu4e 'background)
  (mu4e-search-bookmark
   (mu4e-get-bookmark-query ?u)))

(popup-frame-define my/org-agenda "large-popup")

For this, I add another “elif” to the bash script, to ensure I can see the whole inbox in the popup:

elif
[[ $title =~ "large-popup" && $display == 1 ]]; then
yabai -m window $YABAI_WINDOW_ID –toggle float –move abs:350:130
yabai -m window $YABAI_WINDOW_ID –resize abs:730:600
yabai -m window $YABAI_WINDOW_ID –focus

Custom Org-Agenda View

(defun my/org-agenda (&optional p)
  (interactive "P")
  (org-agenda p "d")
  (org-agenda-redo-all))

(popup-frame-define my/org-agenda "large-popup")

Just don’t forget to add a system-wide keyboard shortcut for each new command!

This post continues a series of video demos in which I show how I use the Emacs package zk for notetaking. The emphasis is on showcasing the practicalities of my process.

——————————————————————————————

In this video, I show how I quickly find notes related to the note I am working on in zk, and then insert the relevant links.

The goal is to find and insert links with a minimum of mental overhead and technical friction, so that I can keep my focus on the ideas and content of the note at hand.

Trying to find a single note among hundreds, and then putting the link in the right spot, could be a task unto itself — a not inconsiderable problem and distraction. But a few handy search functions, and some help from the package Embark, make the matter all but effortless.

DONE Emacs: Getting started with zk, using hydra and an inbox note

This post inaugurates a series of video demos in which I will show how I use the Emacs package zk for notetaking. The emphasis will be on showcasing the practicalities of my process.

——————————————————————————————

In this video I show how I use a hydra as an entry point to my zk. The config for my hydra is here, in my dotfiles.

I also show how I use an “inbox” note as a starting place for new notes. For more on the place of the inbox in a notetaking practice, see section 2.1 of How to Take Smart Notes by Sönke Ahrens.1

In the next video, I will show how I use the function zk-find-file and others in combination with Embark to quickly find notes and insert links — with a minimum of effort!

DONE Emacs: With ace-window and link-hint, open links exactly where you want them

This post describes how the Emacs package link-hint can be combined with ace-window to allow you to select, on-the-fly, which window a link will open in—instead of letting fate, or custom, or Emacs decide for you.

——————————————————————————————

Ace-window is (more than) a fancy window-switcher

The ace-window package, written by abo-abo, offers a handy, full-featured alternative to the built-in window-switching function other-window. The package’s primary function—the titular =ace-window=—overlays a different character (letter or number) over each visible Emacs window, so you can switch to the desired window by simply pressing its assigned character. Very useful, very sleek.

But ace-window is more than just a handy way to switch windows.

The package also offers the ability to perform an action in the selected window before switching to it. In this regard, it follows the design of its parent package avy—also by abo-abo. Instead of simply switching to a window, you can split it first, call switch-to-buffer in it, or even close it. For a demonstration of this aspect of ace-window, see this video from Protesilaos Stavrou.)

The greatness of ace-window truly comes through, however, when it is combined with other packages.

With ace-window and Embark, open files/buffers in specified windows

In his detailed write-up on using Embark, Karthik Chikmagalur showcases a killer mash-up of ace-window and Embark, demonstrating how ace-window can be used for more than just switching windows.

Briefly put, Karthik shows how to incorporate ace-window’s window-selection function into the opening of a file or buffer from the minibuffer so that, as he writes, “any buffer/file/bookmark I open is always placed exactly where I want it to be on the screen.”

An example usage would be:

  1. call find-file
  2. call embark-act on file you wish to open
  3. call my/embark-ace-action-find-file (this is the slightly unwieldy name Karthik gives to the embark/ace-window function)
  4. select desired window with ace-window

Inspired by what Karthik showed was possible, I wanted to incorporate ace-window with link-hint, so that I could choose which window a link would open in. The result is a function called link-hint-aw-select:

Figure 1: Using ’link-hint-aw-select’ to open each of the links in the top left window in a different window. First, the desired link is chosen via character overlay, courtsey of link-hint, then the desired window is chosen via character overlay, courtsey of ace-window.

Figure 1: Using ’link-hint-aw-select’ to open each of the links in the top left window in a different window. First, the desired link is chosen via character overlay, courtsey of link-hint, then the desired window is chosen via character overlay, courtsey of ace-window.

If you’re unfamiliar, link-hint is a package that is conceptually similar to ace-window: it puts a character overlay on visible links in a buffer so that you can choose a link to follow by typing its assigned character. (The conceptual similarity of link-hint and ace-window is not surprising, since both are based on avy.)

Link-hint can identify a lot of different type of links, such as urls, file paths, mailto links, org-links, buttons, and dired filenames. New link types can also be defined by the user. (More on that later.)

As with avy and ace-window, link-hint allows for different actions to performed on the chosen link. However, there are only two default actions for acting on links: open link and copy link.

Defining a new action is done by defining a new function, in this case the above-mentioned link-hint-aw-select, which will be entry-point command for all link types:

(defun link-hint-aw-select ()
  "Use avy to open a link in a window selected with ace-window."
  (interactive)
  (unless
      (avy-with link-hint-aw-select
        (link-hintone :aw-select))
    (message "No visible links")))

Things get more complicated very quickly, however.

Each action must be tailored to each different link type. Take the case of the “open” action, for examples: urls open one way—in a browser—and filepaths open another way—in a relevant application—and email addresses open another way—in a new draft email. Each action is actually many different actions, all roughly similar.

When creating a new action, then, it is necessary to make sure that each link type is associated with a function that will perform the new action in the appropriate way.

The aw-select action for file links is quite simple:

(defun link-hintaw-select-file-link (link)
  (with-demoted-errors "%s"
    (aw-switch-to-window (aw-select nil))
    (find-file link)))

To make the link-hint ecosystem aware of this new association of link-type, action, and function, there is a handy macro:

(link-hint-define-type 'file-link
  :aw-select #'link-hintaw-select-file-link)

If different link-types behave similarly, it is possible to create a macro of our own that will do all of the above very efficiently. This is the case with buttons and dired-filenames, for example:

(defmacro define-link-hint-aw-select (link-type fn)
  `(progn
     (link-hint-define-type ',link-type
       :aw-select #',(intern (concat "link-hint–aw-select-"
                                     (symbol-name link-type))))
     (defun ,(intern (concat "link-hint–aw-select-"
                             (symbol-name link-type))) (_link)
       (with-demoted-errors "%s"
         (if (> (length (aw-window-list)) 1)
             (let ((window (aw-select nil))
                   (buffer (current-buffer))
                   (new-buffer))
               (,fn)
               (setq new-buffer (current-buffer))
               (switch-to-buffer buffer)
               (aw-switch-to-window window)
               (switch-to-buffer new-buffer))
           (link-hint-open-link-at-point))))))

(define-link-hint-aw-select button push-button)
(define-link-hint-aw-select dired-filename dired-find-file)

The same is almost the case with org-links as well, except that by default org-links are opened using find-file-other-window instead of find-file, meaning that the above macro wouldn’t work properly.

I prefer to change this setting globally, so that org-links are opened in the current window, by evaluating the following:

(setf (cdr (assoc 'file org-link-frame-setup)) 'find-file)

This allows me to use the macro:

(define-link-hint-aw-select org-link org-open-at-point)

To leave org’s default behavior in place globally, it is necessary to define a function specifically for org-links, in which the behavior is changed locally in a let-binding:

(defun link-hintaw-select-org-link (_link)
  (let ((org-link-frame-setup
         '((file . find-file))))
    (with-demoted-errors "%s"
      (if (> (length (aw-window-list)) 1)
          (let ((window (aw-select nil))
                (buffer (current-buffer))
                (new-buffer))
            (org-open-at-point)
            (setq new-buffer
                  (current-buffer))
            (switch-to-buffer buffer)
            (aw-switch-to-window window)
            (switch-to-buffer new-buffer))
        (link-hint-open-link-at-point)))))

(link-hint-define-type 'org-link :aw-select #'link-hintaw-select-org-link)

Things are actually even a bit more complicated than this with org-links.

Since org handles different link-types internally, the function link-hint–aw-select-org-link can also be made to handle certain org link-types differently. For example, if you want http/https links to be opened externally, there is no reason to call the aw-select behavior for that type of org-link. This could be accomplished by including something like the following in the function’s “if” conditional:

(not (member (org-element-property :type (org-element-context))
             '("http" "https")))

The whole function would be:

(defun link-hintaw-select-org-link (_link)
  (let ((org-link-frame-setup
         '((file . find-file))))
    (with-demoted-errors "%s"
      (if (and (> (length (aw-window-list)) 1)
               (not (member (org-element-property
                             :type (org-element-context))
                       '("http" "https"))))
          (let ((window (aw-select nil))
                (buffer (current-buffer))
                (new-buffer))
            (org-open-at-point)
            (setq new-buffer
                  (current-buffer))
            (switch-to-buffer buffer)
            (aw-switch-to-window window)
            (switch-to-buffer new-buffer))
        (link-hint-open-link-at-point)))))

Example use-case: note-taking, zettelkasten, etc.

I find this functionality really useful in my note-taking environment, where each of my notes contains several links to other notes. I rarely want to follow a link in the same window as the note where the link appears. I typically want to leave that note where it is and open a link somewhere else. If I have several windows arrayed around my screen, I can use link-hint-aw-select to open the linked note exactly where I want it to be.

To use this with org, the above configuration is enough. If you use another note-taking environment, you may need to define a new link type.

For example, for use with my own Zettelkasten package zk, I define a new link type zk-link and a function zk-link-hint-aw-select, along with other functions necessary to fully incorporate a new link type into the link-hint system:

(defun zk-link-hintzk-link-at-point-p ()
  "Return the ID for the zk-link at the point or nil."
  (and (zkid-at-point)
       (thing-at-point-looking-at zk-link-regexp)))

(defun zk-link-hintnext-zk-link (bound)
  "Find the unext zk-link.
Only search the range between just after the point and BOUND."
  (link-hintnext-regexp zk-id-regexp bound))

(defun link-hintaw-select-zk-link (id)
  (with-demoted-errors "%s"
    (if (> (length (aw-window-list)) 1)
        (let ((window (aw-select nil))
              (buffer (current-buffer))
              (new-buffer))
          (zk-follow-link-at-point id)
          (setq new-buffer
                (current-buffer))
          (switch-to-buffer buffer)
          (aw-switch-to-window window)
          (switch-to-buffer new-buffer))
      (link-hint-open-link-at-point))))

(link-hint-define-type 'zk-link
  :next #'zk-link-hintnext-zk-link
  :at-point-p #'zk-link-hintzk-link-at-point-p
  :open #'zk-follow-link-at-point
  :copy #'kill-new
  :aw-select #'link-hintaw-select-zk-link)

(push 'link-hint-zk-link link-hint-types)

Even though link-hint can be a bit complex under the hood, it is quite effortless, even elegant, when put in combination with ace-window. Give it a try and puzzle over what you had done so long without it.


  1. Sönke Ahrens, How to Take Smart Notes: One Simple Technique to Boost Writing, Learning and Thinking (North Charleston, SC: CreateSpace, 2017). ↩︎


« Older posts
Page 6
Newer posts »