claude-pager

On <2026-06-03 Wed> I somehow bumped into idea to use emacsclient as EDITOR in Claude Code.

Turns out CC respects EDITOR env variable, either from environment or configured in ~/.claude/settings.json:

{"env": {"EDITOR": "emacsclient", "VISUAL": "emacsclient"}}

Even if [Feature Request] Allow configuring external editor for Ctrl-G prompt editing · Issue #18990 · anthropics/claude-code is still in open.

In that issue I spotted this project: gradigit/claude-pager. Fast C transcript pager for Claude Code: no blank Ctrl-G screen, clickable links/files, queued prompt composer. And its sister editor: gradigit/turbodraft.

Tried both. Turbodraft - I don't need another text editor rather than Emacs, so skipping it. But claude-pager looks useful, so I forked it.

First, I wired Emacs into claude-pager; so that it renders transcripts in both CC screen when it waits for the prompt + adds it to Emacs temporary buffer.

This mostly solves clunkiness of copying from claude terminal into Emacs and back - now I press {C-g} in claude and continue editing in Emacs.

Related to Agent SDK billing and parting ways with agent-shell.

This is how my env block looks like in ~/.claude/settings.json.

{
  "env": {
    "EDITOR": "/Users/pavel/.velppa/claude-pager/bin/claude-pager-open",
    "CLAUDE_PAGER_EDITOR": "/Users/pavel/.velppa/claude-pager/emacs/claude-emacs-prompt",
    "CLAUDE_PAGER_EDITOR_TYPE": "gui"
  }
}

{C-g} in claude opens claude-pager-open that generates transcript and pastes it into both terminal and to the temp file to edit in Emacs, then runs claude-emacs-prompt shell file that calls elisp file which loads into the buffer, assigns keybindings etc.

Setup involves adding hooks:

SessionStart hook:
     26:            "command": "/Users/pavel/.velppa/claude-pager/shim/save-session-transcript.sh"

Then I rewrote the binary from C to Zig. Rewrite used Superpowers to develop a spec first, then launch a series of agents. It took 4+ hours for CC to finish rewrite, it was ready in the morning. It used all extra usage (42 EUR) in fast mode in first 20 minutes.

7000 loc in C => ??? in Zig.

Now I understand the C well.

Another interesting project from the same author:

  • gradigit/confetti - Lightweight confetti animation for macOS — fire colorful confetti from your screen corners

Copy buffer-name with easy-kill

easy-kill Emacs package allows killing multiple things, switching between them on the fly. I use it to quickly grab the buffer-file-name (full path or file name only), using {M-w b}.

github
leoliu/easy-kill

Keybindings:

{M-w} without active region
copy current line, same as {x y} in meow-normal-mode
{M-w C-w} without active region
kill current line, same as {x s} in meow-normal-mode
{M-w b}
copy current full path to buffer-file-name
{M-w b 0}
copy current buffer-file-name
{M-w b -}
copy current directory

On <2026-06-04 Thu> I added {M-w B} to copy current buffer-name, useful to paste into Claude Code (running under ghostel for me).

Here's the configuration block:

(use-package easy-kill
  :config
  (keymap-global-set "<remap> <kill-ring-save>" #'easy-kill)
  (defun easy-kill-on-buffer-name (_n)
    "Get current `buffer-name'."
    (if (easy-kill-get mark)
        (easy-kill-echo "Not supported in `easy-mark'")
      (easy-kill-adjust-candidate 'buffer-name (buffer-name))))
  (add-to-list 'easy-kill-alist '(?B buffer-name)))
  1. Define function easy-kill-on-buffer-name.
  2. Add binding to 'easy-kill-alist.

kids-unicode-mode

Emacs supports Unicode:

(insert-char (char-from-name "T-REX")) ;; 🦖

To have fun with kids, I wrapped kids-unicode minor mode that brings keymap redefining every key to insert emoji. Enable with {M-x kids-unicode-mode} and have fun typing, encoding names with emojis and train memory.

(use-package kids-unicode-mode :ensure nil)

Source: kids-unicode-mode.el

Showcase: 260516--kids-unicode-mode__screenshot.png

terraform-ts-mode

At work I work with Terraform, so need to edit quite a lot of tf files in GNU Emacs. There's no built-in terraform-ts-mode Emacs package and I don't want to install regexp-based terraform-mode package

Existing terraform-ts-mode is this: kgrotel/terraform-ts-mode, which claims being experimental. So it's a good opportunity to learn how to create major modes using Tree Sitter and on <2026-05-05 Tue> I built my own with Claude Code.

Installation: put terraform-ts-mode.el to load-path.

Usage:

(use-package terraform-ts-mode :ensure nil
  :init
  (add-to-list 'major-mode-remap-alist '(terraform-mode . terraform-ts-mode)))

With this knowledge, the next step is [TK: try mickeynp/combobulate package].

Window configuration on m4pro

MacBook Pro M4 Pro has larger screen and windows in Emacs behave differently, often ending up with 6 windows at once. That's not what I want.

Time to learn Window management in Emacs.

On <2026-04-21 Tue> I switched to ace-window from other-window and keeping track of what has changed.

Keybindings:

Good:

Bad:

Things to try:

Advice to org-roam-dailies-goto-today

Currently, when calling org-roam-dailies-goto-today, {C-c n d} for me, Emacs switches to the daily note buffer, but moves point to (point-min). I want to advice to first check if the buffer with "YYYY-MM-DD.org" (for today) already exists, and if so, switch to this buffer. Related to Org-roam.

Here's an advice function to achieve that:

(defun my-org-roam-dailies-goto-today-advice (orig-fun &rest args)
  "Switch to today's daily note buffer/window if it exists, otherwise create it.
Preserves point position if buffer already exists."
  (let* ((today (format-time-string "%Y-%m-%d"))
         (daily-buffer-name (concat today ".org"))
         (existing-buffer (get-buffer daily-buffer-name))
         (existing-window (and existing-buffer
                               (get-buffer-window existing-buffer t))))
    (cond
     ;; Buffer is already displayed in a window - switch to that window
     (existing-window
      (select-window existing-window))
     ;; Buffer exists but not displayed - switch to it in current window
     (existing-buffer
      (switch-to-buffer existing-buffer))
     ;; Buffer doesn't exist - create it
     (t
      (apply orig-fun args)))))

(advice-add 'org-roam-dailies-goto-today
            :around #'my-org-roam-dailies-goto-today-advice)

This advice:

  1. Checks for buffer by name (not file path)
  2. If the buffer is displayed in any window (even in another frame), selects that window
  3. If the buffer exists but isn't displayed, switches to it in the current window
  4. Otherwise calls the original function

To remove the advice later:

(advice-remove 'org-roam-dailies-goto-today
               #'my-org-roam-dailies-goto-today-advice)

Improve public notes

Next round of improving Textpod on <2026-04-15 Wed>.

Deploy publicly –

Introduced --base-path flag, to serve notes under sub-path.

To start Textpod with sub-path, use:

textpod --token $TEXTPOD_TOKEN --base-path notes
Publish textpod.el to the repository

textpod.el is a companion Emacs package for quick publishing notes from Org Mode documents.

Install it like this:

(use-package textpod
  :config
  (setq textpod-token (cadr (auth-source-user-and-password "finita.myaddr.dev" "textpod")))
  (setq textpod-url "/notes"))

Then these helper functions do the heavy-lifting:

  • textpod-org-region-to-note: Convert the Org region between BEG and END to Markdown and send to Textpod.
  • textpod-org-heading-to-note: Send the current heading’s subtree to Textpod.
  • textpod-open-current-note: Open the current heading’s Textpod note in a browser.

When creating a note, textpod.el sets TEXTPOD_ID property that's equal to node'id.

Change separator in notes.md to ^L

Default separator \n\n---\n\n is fragile as it requires exactly two newlines before / after the note. I often messed up notes when editing notes.md file manually. To fix that, I replaced it with ^L character (insert in Emacs via C-q C-l) which usually represents new pages.

Fix attachments links

Attachement links need to respect --base-path flag.

Handling attachments

My current workflow:

  • Assets are stored next to Notes under assets/ directory

  • I can quickly search for them, then open in Emacs/qView:

    find ../assets -name "*prefix-in-emacs*"
    
    ../assets/m-g-prefix-in-emacs.png
    ../assets/m-s-prefix-in-emacs.png
    
  • I can reference an asset as an image: img

  • An image is a link to an image file that does not have a description part (Images (The Org Manual)).

  • Textpod already support images. What happens when I drag-n-drop an image file to the textarea? Textpod copies them under attachments directory.

What I want:

  • When creating a public note containing an image via API (using textpod.el), copy the asset to assets/ directory, mimicing the directory structure in the notes.
  • I need another API endpoint - POST /notes/assets/<name>, accepting binary as payload.

Okay, I implemented it, let's test.

textpod-org-heading-to-note

Prompt: Implement textpod-org-heading-to-note function, it should select current (top) heading - the one that has TEXTPOD_ID parameter, and call textpod-org-region-to-note on it.

The command {M-x textpod-org-heading-to-note} works!

Should the textpod id be just id?

Currently I use TEXTPOD_ID property. If using ID property, notes will automatically become Org-roam nodes. Currently IDs are generated as timestamps via (setq org-id-method 'ts) with nanoseconds precision. Then it will come with seconds precision. I will need to a prefix, to be able to quickly find them (e.g. with {M-x deadgrep}).

Wrap h3 into details on index page

To declutter the index page, h3 markdown headings (h2 Org headings) are wrapped into <details> tag. The note page renders normally, showing table of contents on the left sidebar.

M-s and M-g prefixes in Emacs   

· :Blog:

Let's talk about two semantic and useful prefixes in GNU Emacs - M-s and M-g. Semantics is there - M-s is for "search", M-g is for "go".

M-g is for Go
M-g l
go to line
M-g c
go to char
M-g g
go go go
M-g M-g
also "go go go", for convenience, hold Alt and press g g twice.
M-g w
go to word, asks for the first character of a word and jumps there.
M-g C-h
I ran out of remembering keybindings, so this one is list all of them – add C-h after any prefix and Emacs will show you the help. No need to use which-key unless necessary, you're in control - you'll memorise things only looking first into memory, if missing - take action to learn (and thus remember). which-key is a false friend for new Emacser, beware!

Checking the keymap, we see many aliases like M-g M-g, so M-g M-c, M-g M-l, M-g M-n and so on, they are for mnemonical convenience.

Most of movements run avy, which can do anything, it's an awesome way of arbitrary movements and beyond.

m-g-prefix-in-emacs.png
M-s is for Search

M-s is an entrypoint to search-related things. 95% of the time I use M-s d (deadgrep) and M-s o (occur).

M-s d runs deadgrep, a frontend for ripgrep for searching in the current project. I use it all the time, and nowadays with rise of LLM, making your project greppable is must have to enable AI to do sensible changes in the project.

M-s o is mighty M-x occur, but adjusted to "do what I mean" semantics – if selection is active, it runs "occur" on it, otherwise prompts for input.

(use-package emacs
  :config
  (defun occur-dwim (&optional _)
    "Run occur with Active Region if any, otherwise regular occur."
    (interactive)
    (if (use-region-p)
        (occur (buffer-substring-no-properties (region-beginning) (region-end)))
      (call-interactively 'occur)))
  (keymap-global-set "M-s o" #'occur-dwim))

M-s h is highlighing prefix, which deserves a separate post. So see you later, cheers!

m-s-prefix-in-emacs.png