diff
將 diff 提示列作為外掛使用:/diff 會把工作階段尚未提交的變更,依檔案及其 hunks 顯示在 transcript 旁,Claude 編輯和執行指令時會重新整理;第一次編輯會在終端機夠寬時開啟,檔案的詢問按鈕會在下一個提示中帶上該檔案的 hunks。
關於這個 mod
diff
將 diff 提示列作為外掛使用:/diff 會在 transcript 旁開啟工作階段尚未提交的變更,每個變更檔案一列,下面列出每個檔案的所有 hunks;也可以再次關閉。每次切換都會在 transcript 留下 Diff panel shown 或 Diff panel hidden。標頭、檔案清單和切換按鈕保持不動;滾輪在 hunks 上移動時每次移動三列,在超過八列的檔案清單上移動時每次移動一個檔案(外掛自行處理提示列的 ui.scroll);點擊某列會把該檔案的 hunks 放到頂端。清單也會回應內建清單的按鍵(ctrl+up/ctrl+down、opt+up/opt+down),ctrl+x b 會像內建快速鍵一樣向前移動比較基準:兩者都透過宣告引擎自身操作的 Buttons 完成。提示列會在 Claude 編輯和執行 shell 指令時重新整理;開啟期間也會輪詢 repo 的 HEAD,所以在其他地方完成的提交或 checkout 也會顯示。
主迴圈第一次成功編輯工作階段時會自行開啟提示列,就像內建面板在第一次 checkpoint 時開啟一樣:版面會根據每個繪圖的 viewport 所說明的全螢幕版面,把它停靠在 transcript 旁;終端機夠寬(使用者從未選擇時為 144 欄,之前保持開啟時為 110 欄;使用者關閉過就尊重該選擇),而且檔案 checkpointing 已開啟。子代理程式的編輯不會開啟面板;如果介面沒有說明,面板也不會自行開啟。停靠的面板會先 fetch 再開啟,就像內建面板會預熱資料,因此不會停在 Loading diff…;如果引擎留下等待中但尚未繪製的開啟請求,就會撤回它,之後的 resize 也不會把它放回去,下一次編輯會重新詢問。恢復或繼續的工作階段若 transcript 已有這類編輯,在寬度確定後也會依相同條件開啟面板,就像內建面板會根據恢復的歷史開啟一樣。
在全螢幕版面下,寬度小於 110 欄的終端機會顯示內建面板要求加寬的那一行,不會開啟任何內容。沒有該版面(CLAUDE_CODE_NO_FLICKER=0,/diff 會從指令的 presentation 讀取這項設定)時,面板會在任何寬度內嵌開啟、取得焦點,並依內容高度(開啟請求的 rows)顯示,形狀仿照內建對話框:標題、數量、圍繞選取項目一次顯示五個檔案列(❯ 表示焦點環開始的位置;外掛遵循焦點環透過 ui.focus 的移動,並像內建介面一樣重新將這些列置中)、按鍵提示;Enter 只顯示該檔案的 hunks,Escape 退回清單後關閉,留下 Diff dialog dismissed;面板開啟時會暫存提示訊息。檔案的詢問按鈕會讓該檔案進入待處理狀態:它的 hunks 會作為上下文帶到下一個提示,而且只帶一次。
提示列會將工作樹與 HEAD 比較,預設以工作階段開始時的狀態切開(引擎在 $.session.usage() 提供這個起點,因此恢復的工作階段會保留第一次的起點,而 /clear 會重新開始);也可以直接與 HEAD 比較,或與預設分支的 merge-base 比較。標頭下方的基準行會在基準不是工作階段基準時標出它,選擇會依 repo 儲存在外掛的 store 中。選擇器可以顯示較早回合的編輯,而不是工作樹;這些編輯從工作階段訊息讀取。工作階段開始前就已變更的檔案(依時間戳記,在面板第一次讀取 repo 時已經 dirty 的路徑中篩選),以及雜訊檔案(lockfile、產生檔案和測試檔案)會分開列出,並折疊到使用者要求查看為止;重新命名會依 git 的輸出方式列出。在 git repo 之外,/diff 會說明這點,不做其他事。
內建面板會執行 git 的時機,這裡也相同:工作階段開始時不執行;當 /diff 或第一次有空間開啟面板的編輯首次需要 repo 時,在工作階段啟動目錄中執行一次 git rev-parse(「不是 repo」的結果也會保留,直到 /clear 或 /resume 忘掉它);工作樹只會在面板開啟後、一次已落地的編輯或執行過 shell 指令之後,透過一次 fetch 讀取。內建面板沒有對應物的唯一讀取,是面板第一次 fetch 時的 git status;它用來代替內建介面依移動檔案的時間標出的變更時間。
hooks/register.ts 是模組;hooks/ 下的所有內容都是它的組成部分。
接入的事件
| 事件 | 掛勾做什麼 |
| --- | --- |
| session.start | 綁定引擎一次並註冊 /diff(如果另一個 /diff 已列出,就讓外掛保持閒置);不詢問 repo,等 /diff 或第一次編輯到來時再固定 repo;在它的分派之外讀取 transcript,對已經編輯過的恢復工作階段,像第一次編輯一樣開啟面板。 |
| ui.render of PromptHint | 讀取終端機寬度以及版面是否會停靠面板;兩者決定第一次編輯是否開啟面板。 |
| ui.render of Pane | 繪製面板:停靠時顯示標頭、基準行、來源選擇器、檔案清單和切換按鈕,覆蓋 hunks 的視窗;內嵌時繪製對話框。 |
| command.run of diff | 在 repo 尚未固定時固定它,開啟或關閉面板(取得焦點;沒有全螢幕版面時按 Escape 關閉),說明是哪一種,並記住選擇。 |
| ui.close of the pane | 從對話框的詳細檢視退回,而不是關閉面板;否則記住使用者透過 /diff 關閉面板的選擇。 |
| ui.scroll of the pane | 停靠時讓 hunks 在固定的標頭和清單下方移動(滾輪每次三列,頁面按鍵每次一頁);滾輪位於清單上時移動清單,並保持引擎視窗不動。 |
| ui.focus in the pane | 在對話框清單中選取焦點環落到的檔案,以它為中心重新排列五列,並讓焦點環落在該列現在的位置。 |
| command.run of clear, resume | 關閉面板並忘掉工作階段狀態,也一併忘掉已固定的 repo。 |
| tool.call of Edit, Write, NotebookEdit | 編輯落地後(未被拒絕,也未失敗)重新整理開啟的面板;主迴圈第一次出現這類編輯時,只要終端機有空間且 checkpointing 已開啟,就會開啟面板,並在此時固定 repo。 |
| tool.call of Bash, PowerShell | 指令未被拒絕後重新整理開啟的面板;失敗及中斷的指令也包括在內。 |
| prompt.submit | 將已待處理檔案的 hunks 加入提示的上下文,然後取消待處理狀態。 |
它呼叫 $ 的內容
clock.after、clock.every、clock.now、command.register、env.get
(CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING)、fs.list、fs.read、fs.stat、
process.run(唯讀的 git)、session.id、session.messages、
settings.read、store.get、store.set、telemetry.log、telemetry.mark、
ui.close、ui.invalidate、ui.log、ui.open、ui.resolve、ui.status。
$.telemetry 是 telemetry 外掛的名詞;如果它不存在,相關列會被丟棄,其他內容不會改變。
試用
claude --plugin-dir /path/to/diff
然後在有修改檔案的 git repo 中執行 /diff。
安裝
請先查看作者 README,確認 marketplace 與外掛名稱;指令可能隨儲存庫結構而變動。
claude plugin marketplace add anthropics/claude-code claude plugin install diff
原文 / README
diff
The diff pane as a plugin: /diff opens the session's uncommitted changes
beside the transcript, one row per changed file and every file's hunks
beneath, and closes it again; each toggle leaves Diff panel shown or
Diff panel hidden in the transcript. The header, the file list and its
toggles stay put while the wheel moves the hunks under them three rows a
tick, or the list a file a tick while the wheel is over a list longer than
its eight rows (the plugin answers the pane's ui.scroll itself); a row's
click puts that file's hunks at the top; the list also scrolls under the
built-in's list keys (ctrl+up/ctrl+down, opt+up/opt+down), and
ctrl+x b moves the comparison base on, as the built-in's chord does: both
through Buttons that declare the engine's own actions. The pane refreshes
as Claude edits and runs shell commands, and while it is open it polls
the repository's HEAD so a commit or checkout made elsewhere shows too.
The main loop's first successful edit of a session opens the pane by
itself, as the built-in panel opens on its first checkpoint: where the
layout docks it beside the transcript (the fullscreen layout, which each
drawing's viewport says), the terminal is wide enough (144 columns when
the person never chose, 110 when they kept it open before; a person who
closed it is left alone) and file checkpointing is on; a subagent's edit
opens nothing, and where the surface does not say, nothing opens by
itself. A docked pane fetches before it opens, as the built-in panel
primes its data, so it never lands on Loading diff…; an open the engine
leaves waiting undrawn is withdrawn, so no later resize seats it, and the
next edit asks again. A session resumed or continued whose transcript
already holds such an edit opens the pane on the same terms as soon as the
width is known, as the built-in opens on the history it restores.
Under the fullscreen layout a terminal under 110 columns gets the
built-in's line asking for a wider one and nothing opens. Without that
layout (CLAUDE_CODE_NO_FLICKER=0, which /diff learns from the command's
presentation) the pane opens inline at any width, focused and as tall
as its content (the open's rows), in the built-in dialog's shape: the
title, the count, five file rows at a time round the selected one (❯,
where the focus ring starts; the plugin follows the ring's walk through
ui.focus and re-centres the rows as the built-in does), the key hints;
Enter shows that file's hunks alone,
Escape backs out to the list and then closes, leaving Diff dialog dismissed; toasts are held while it is up. A file's ask button arms that
file: its hunks ride the next prompt as context, once.
The pane compares the working tree against HEAD, split at the session's
start (the default; the start the engine gives in $.session.usage(), so a
resumed session keeps its first and /clear begins anew), against HEAD
plainly, or against the merge-base with
the default branch; the base line under the header names a base other than
the session's, and the choice is kept per repository in the plugin's store.
A picker shows one earlier turn's edits instead of the working tree, read
from the session's messages. Files that changed before the session started
(by their timestamp, among the paths already dirty when the pane first
read the repository), and noise (lockfiles, generated and test files), are
listed apart and folded until asked for; a rename lists as git prints it.
Outside a git repository /diff says so and does nothing else.
Git runs when the built-in panel's would: nothing at the session's start;
one git rev-parse, in the directory the session started in, when /diff
or the first edit a pane has room to open on first needs the repository
(an answer of no repository is kept too, until /clear or /resume
forgets it); and the working tree is read only by a fetch for a pane that
is open, after an edit that landed or a shell command that ran. The one
read the built-in has no counterpart for is a git status at a pane's
first fetch, which stands in for the change time the built-in dates a
moved file by.
hooks/register.ts is the module; everything under hooks/ is its parts.
What it hooks
| event | what the hook does |
| --- | --- |
| session.start | Binds the engine once and registers /diff (a session where another /diff is listed leaves the plugin idle); asks nothing of the repository, which /diff or the first edit pins when it comes; off its dispatch, reads the transcript, and for a resumed session whose turns edited opens the pane as the first edit would. |
| ui.render of PromptHint | Reads the terminal's width and whether its layout docks a pane, which decide whether the first edit opens the pane. |
| ui.render of Pane | Draws the pane: docked, the header, base line, source picker, file list and toggles over the window of hunks; inline, the dialog. |
| command.run of diff | Pins the repository when none is, opens or closes the pane (focused and closing on Escape without the fullscreen layout), says which, and remembers the choice. |
| ui.close of the pane | Backs out of the dialog's detail view instead of closing; else remembers the person's close as /diff's. |
| ui.scroll of the pane | Docked, moves the hunks under the pinned header and list (three rows a wheel tick, a page a page key), or the list when the wheel is over it, and keeps the engine's window still. |
| ui.focus in the pane | In the dialog's list, selects the file the ring lands on, re-centres the five rows on it, and lands the ring where that row now sits. |
| command.run of clear, resume | Closes the pane and forgets the session's state, the pinned repository with it. |
| tool.call of Edit, Write, NotebookEdit | After an edit that landed (not refused, not failed), refreshes an open pane; the main loop's first such edit opens it, pinning the repository then if the terminal has the room and checkpointing is on. |
| tool.call of Bash, PowerShell | After a command that was not refused, failed and interrupted ones too, refreshes an open pane. |
| prompt.submit | Adds the armed file's hunks to the prompt's context and disarms. |
What it calls on $
clock.after, clock.every, clock.now, command.register, env.get
(CLAUDE_CODE_DISABLE_FILE_CHECKPOINTING), fs.list, fs.read, fs.stat,
process.run (git, read-only), session.id, session.messages,
settings.read, store.get, store.set, telemetry.log, telemetry.mark,
ui.close, ui.invalidate, ui.log, ui.open, ui.resolve, ui.status.
$.telemetry is the telemetry plugin's noun; where it is absent the rows
are dropped and nothing else changes.
Try it
claude --plugin-dir /path/to/diff
then /diff inside a git repository with a modified file.