diff
プラグインとしての diff ペインです。`/diff` はセッションの未コミット変更を transcript の横に、ファイルごとに hunks 付きで開き、Claude が編集やコマンド実行をすると更新します。最初の編集は端末に十分な幅がある場合に開き、ファイルの ask ボタンは次のプロンプトにそのファイルの hunks を載せます。
この mod について
diff
プラグインとしての diff ペインです。/diff はセッションの未コミット変更を transcript の横に開き、変更されたファイルごとに 1 行、その下に各ファイルの hunks を表示して、再び閉じます。切り替えるたびに transcript へ Diff panel shown または Diff panel hidden を残します。ヘッダー、ファイルリスト、切り替えはその場に留まり、ホイールを hunks 上で動かすと 1 回につき 3 行、8 行を超えるリスト上で動かすと 1 回につき 1 ファイル分だけリストが動きます(プラグイン自身がペインの ui.scroll に応答します)。行をクリックすると、そのファイルの hunks が先頭に来ます。リストは組み込みのリストキー(ctrl+up/ctrl+down、opt+up/opt+down)でもスクロールし、ctrl+x b は組み込みのコードと同じように比較ベースを先へ移動します。どちらもエンジン独自のアクションを宣言する Buttons を通ります。ペインは Claude が編集や shell コマンドを実行すると更新され、開いている間はリポジトリの HEAD もポーリングするため、別の場所で行われたコミットや checkout も表示します。
メインループでセッションの最初の成功した編集が行われると、組み込みパネルが最初の checkpoint で開くのと同じようにペインを自動で開きます。レイアウトがペインを transcript の横にドックし(各描画の viewport が示すフルスクリーンレイアウト)、端末に十分な幅があり(ユーザーが選んでいなければ 144 列、以前に開いたままにしていれば 110 列、閉じた人の選択は尊重)、ファイル checkpointing が有効であることが条件です。サブエージェントの編集では開きません。画面に記載がない場合も自動では開きません。ドックされたペインは開く前に fetch します。組み込みパネルがデータを準備するのと同じなので、Loading diff… のまま表示されることはありません。エンジンが未描画のまま待機させた開く要求は取り下げるため、その後の resize でも配置されず、次の編集で再度尋ねます。transcript にそのような編集がすでにある復帰・継続セッションは、幅が分かり次第、同じ条件でペインを開きます。
フルスクリーンレイアウトで端末が 110 列未満の場合、組み込みの「幅を広げてください」という行だけを表示し、何も開きません。そのレイアウトがない場合(CLAUDE_CODE_NO_FLICKER=0。これは /diff がコマンドの presentation から読み取ります)、ペインは幅に関係なくインラインで開き、フォーカスされ、内容の高さ(開く操作の rows)になります。形は組み込みダイアログに合わせ、タイトル、件数、選択中の周囲に同時に表示する 5 つのファイル行(❯ はフォーカスリングの開始位置。プラグインは ui.focus によるリングの移動に従い、組み込みと同じように行を再センタリングします)、キーのヒントを表示します。Enter ではそのファイルの hunks だけを表示し、Escape ではリストへ戻ってから閉じ、Diff dialog dismissed を残します。表示中は toast を保留します。ファイルの ask ボタンはそのファイルを武装し、hunks を次のプロンプトにコンテキストとして 1 回だけ載せます。
ペインは作業ツリーを HEAD と比較します。デフォルトではセッション開始時で分割します(エンジンが $.session.usage() で渡す開始点なので、復帰したセッションは最初の開始点を保持し、/clear では新しく始まります)。HEAD そのものとの比較、またはデフォルトブランチとの merge-base との比較もできます。ヘッダー下のベース行はセッションのものと異なるベースを示し、選択はリポジトリごとにプラグインの store へ保存します。ピッカーでは作業ツリーの代わりに、セッションメッセージから読んだ以前のターンの編集を表示できます。セッション開始前に変更されていたファイル(ペインが最初にリポジトリを読み込んだときに、すでに dirty だったパスの中からタイムスタンプで判定)と、ノイズ(lockfile、生成ファイル、テストファイル)は分けて表示し、要求されるまで折りたたみます。rename は git の出力どおりに表示します。git リポジトリ外では /diff はその旨だけを示し、それ以上は何もしません。
組み込みパネルが git を実行するタイミングも同じです。セッション開始時には何もしません。/diff または、ペインを開く幅が初めてある最初の編集でリポジトリが必要になったとき、セッション開始ディレクトリで git rev-parse を 1 回だけ実行します(リポジトリがないという結果も /clear または /resume まで保持します)。作業ツリーは、開いているペインで編集が確定した後、または shell コマンドが実行された後の fetch でのみ読み取ります。組み込みに対応するものがない唯一の読み取りは、ペインの最初の fetch での git status です。これは組み込みが移動したファイルの変更時刻を日付として扱う代わりになります。
hooks/register.ts がモジュールで、hooks/ 以下はすべてその部品です。
フックするもの
| event | フックの動作 |
| --- | --- |
| session.start | エンジンを 1 回バインドして /diff を登録します(別の /diff が一覧にあるセッションではプラグインは待機)。リポジトリには何も尋ねず、/diff または最初の編集が来た時点で固定します。ディスパッチ外で transcript を読み、編集したターンがある復帰セッションでは最初の編集と同じように開きます。 |
| ui.render of PromptHint | 端末の幅と、レイアウトがペインをドックするかを読み、最初の編集で開くかどうかを決めます。 |
| ui.render of Pane | ペインを描画します。ドック時はヘッダー、ベース行、ソースピッカー、ファイルリスト、切り替えを hunks のウィンドウ上に描き、インライン時はダイアログを描きます。 |
| command.run of diff | リポジトリが未固定なら固定し、ペインを開閉します(フォーカスし、フルスクリーンレイアウトなしでは Escape で閉じる)。どちらになったかを伝え、選択を記憶します。 |
| ui.close of the pane | ダイアログの詳細表示から戻り、閉じる処理はしません。それ以外では、ユーザーが /diff で閉じたことを記憶します。 |
| ui.scroll of the pane | ドック時は固定したヘッダーとリストの下で hunks を動かします(ホイールは 1 回 3 行、ページキーは 1 ページ)。ホイールがリスト上ならリストを動かし、エンジンのウィンドウは静止させます。 |
| ui.focus in the pane | ダイアログのリストでリングが到達したファイルを選び、そのファイルを中心に 5 行を再配置し、リングをその行の現在位置に置きます。 |
| command.run of clear, resume | ペインを閉じ、セッションの状態と固定したリポジトリを忘れます。 |
| tool.call of Edit, Write, NotebookEdit | 編集が確定した後(拒否も失敗もしていない場合)、開いているペインを更新します。メインループでの最初の編集なら、端末に幅があり checkpointing が有効なときにペインを開き、その時点でリポジトリを固定します。 |
| 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 リポジトリで /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.
