I’ve been using Claude for almost two years now, and about six months ago I switched completely to claude-code for most
coding activities. I was initially using gptel and ellama, but at some point I came across claude-code.el, which allowed
me to smoothly integrate Claude Code into Emacs. In this post I’ll share my customizations — yes, most of them were
generated by Claude.
Setup
#
Dependencies: inheritenv and eat as terminal backend.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
(use-package inheritenv
:straight (:type git :host github :repo "purcell/inheritenv"))
(use-package eat
:defer t
:commands (eat eat-project eat-other-window)
:straight (:type git
:host codeberg
:repo "akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi"
"*.ti" ("terminfo/e" "terminfo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el"))))
(use-package claude-code
:straight (:type git :host github :repo "manzaltu/claude-code.el"
:branch "main" :depth 1
:files ("*.el" (:exclude "images/*")))
:bind-keymap
("C-x c" . claude-code-command-map)
:bind
(:repeat-map my-claude-code-map ("M" . claude-code-cycle-mode))
:config
(setq claude-code-terminal-backend 'eat)
(define-key claude-code-command-map (kbd "p") #'claude-code-send-command)
(define-key claude-code-command-map (kbd "r") #'my/claude-code-send-region)
(define-key claude-code-command-map (kbd "R") #'my/claude-code-send-region-with-prompt)
(define-key claude-code-command-map (kbd "I") #'my/claude-code-send-region-select-instance)
(define-key claude-code-command-map (kbd "X") #'my/claude-code-send-command-with-context-select-instance))
|
Code Snippet 1:
Basic setup
Table 1:
Key bindings overview
| Key binding |
Command |
Description |
C-x c |
(prefix) claude-code-command-map |
Entry point for all Claude Code commands |
C-x c p |
claude-code-send-command |
Send a plain prompt to Claude |
C-x c r |
my/claude-code-send-region |
Send selected region (fenced) to the active Claude buffer, no prompt |
C-x c R |
my/claude-code-send-region-with-prompt |
Send selected region (fenced) + prompt to the active Claude buffer |
C-x c I |
my/claude-code-send-region-select-instance |
Send selected region + prompt, choose which Claude buffer to send to |
C-x c X |
my/claude-code-send-command-with-context-select-instance |
Send a command with file/region context to a chosen buffer |
M (repeat) |
claude-code-cycle-mode |
Cycle through auto-accept / plan / confirm modes (repeat map) |
Sending a region
#
Select a region and send it to Claude, optionally with a prompt. my/claude-code-send-region
sends the raw region fenced in backticks. my/claude-code-send-region-with-prompt additionally
reads a prompt from the minibuffer and appends it after the fence.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
(defun my/claude-code--build-region-cmd (beg end)
"Build the command string for sending region BEG to END with a prompt."
(let* ((region-text (buffer-substring-no-properties beg end))
(prompt (read-string "Prompt for Claude: " nil 'claude-code-command-history)))
(format "```\n%s\n```\n\n%s"
region-text
prompt)))
(defun my/claude-code-send-region (beg end)
"Send region to Claude without a prompt."
(interactive "r")
(unless (use-region-p)
(user-error "No region selected"))
(let ((region-text (buffer-substring-no-properties beg end)))
(claude-code--do-send-command (format "```\n%s\n```" region-text))))
(defun my/claude-code-send-region-with-prompt (beg end)
"Send region to Claude wrapped in delimiters, then ask for a prompt."
(interactive "r")
(unless (use-region-p)
(user-error "No region selected"))
(claude-code--do-send-command (my/claude-code--build-region-cmd beg end)))
|
Sending file context
#
my/claude-code-send-command-with-context-select-instance attaches the current file reference (or
line range if a region is active) to a prompt, then routes it to a chosen Claude buffer.
1
2
3
4
5
6
7
8
9
10
11
12
|
(defun my/claude-code-send-command-with-context-select-instance ()
"Like `claude-code-send-command-with-context' but prompt for Claude instance."
(interactive)
(let* ((cmd (read-string "Claude command: " nil 'claude-code-command-history))
(file-ref (if (use-region-p)
(claude-code--format-file-reference
nil
(line-number-at-pos (region-beginning) t)
(line-number-at-pos (region-end) t))
(claude-code--format-file-reference)))
(cmd-with-context (if file-ref (format "%s\n%s" cmd file-ref) cmd)))
(my/claude-code--send-to-instance cmd-with-context)))
|
Selecting a different instance
#
When running multiple Claude sessions in parallel (one per project), these functions let you pick
which buffer to target rather than always defaulting to the current one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
(defun my/claude-code--send-to-instance (cmd)
"Prompt for a Claude instance and send CMD to it."
(let* ((all-buffers (claude-code--find-all-claude-buffers))
(target (claude-code--select-buffer-from-choices
"Send to Claude instance: " all-buffers)))
(if target
(with-current-buffer target
(claude-code--term-send-string claude-code-terminal-backend cmd)
(sit-for 0.1)
(claude-code--term-send-string claude-code-terminal-backend (kbd "RET"))
(display-buffer target))
(user-error "No Claude instance selected"))))
(defun my/claude-code-send-region-select-instance (beg end)
"Like `my/claude-code-send-region-with-prompt' but prompt for Claude instance."
(interactive "r")
(unless (use-region-p)
(user-error "No region selected"))
(my/claude-code--send-to-instance (my/claude-code--build-region-cmd beg end)))
|
Switching between profiles
#
I have two Claude accounts: personal (claude.ai) and a corporate Claude proxy at work.
The work account uses a separate settings.work.json with a different ANTHROPIC_BASE_URL
and model names.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
(defvar claude-code-current-profile "personal"
"Current Claude Code profile (personal or work).")
(defun claude-code-use-work-account ()
"Switch Claude Code to use work account settings."
(interactive)
(setq claude-code-program-switches
(list "--settings" (expand-file-name "~/.claude/settings.work.json")))
(setq claude-code-current-profile "work")
(message "Claude Code: Switched to work account. Start a new Claude session."))
(defun claude-code-use-personal-account ()
"Switch Claude Code to use personal account settings (default)."
(interactive)
(setq claude-code-program-switches nil)
(setq claude-code-current-profile "personal")
(message "Claude Code: Switched to personal account. Start a new Claude session."))
(defun claude-code-toggle-account ()
"Toggle between personal and work Claude Code accounts."
(interactive)
(if (string= claude-code-current-profile "personal")
(claude-code-use-work-account)
(claude-code-use-personal-account)))
(defun claude-code-show-current-profile ()
"Display the current Claude Code profile."
(interactive)
(let ((account claude-code-current-profile)
(switches (if claude-code-program-switches
(format "Settings: %s" claude-code-program-switches)
"Default settings (personal)")))
(message "Claude Code Profile: %s | %s" account switches)))
|
claude-code-toggle-account flips the profile; starting a new Claude session picks up the
new settings.