Skip to main content

MagitView

Trait MagitView 

Source
pub trait MagitView:
    Send
    + Sync
    + 'static {
Show 15 methods // Required method fn refresh(&self) -> Option<Effect>; // Provided methods fn refresh_restoring(&self, site: HunkSite) -> Option<Effect> { ... } fn stage(&self, cursor: Position) -> Option<Effect> { ... } fn stage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect> { ... } fn unstage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect> { ... } fn unstage(&self, cursor: Position) -> Option<Effect> { ... } fn diff_source(&self, cursor: Position) -> Option<DiffSource> { ... } fn commit_at_cursor(&self, cursor: Position) -> Option<String> { ... } fn stash_at_cursor(&self, cursor: Position) -> Option<usize> { ... } fn diff_target(&self, path: &Path, cursor: Position) -> Option<Effect> { ... } fn visit_at_cursor(&self, cursor: Position) -> Option<Effect> { ... } fn workdir(&self) -> Option<PathBuf> { ... } fn file_lines( &self, store: &BufferStoreHandle, buffer: BufferId, ) -> Option<Vec<u32>> { ... } fn argument_flags(&self) -> &'static [RemoteFlag] { ... } fn refresh_with_args(&self, extra: Vec<String>) -> Option<Effect> { ... }
}
Expand description

A magit buffer’s view behaviour, published per buffer alongside its state.

Why this exists. Several magit modes bind the same action — action:magit-refresh (gr) has five registrants (status, branch, stash, diff, log). Per-activation registration hid the collision: only the active buffer’s mode had a handler installed at any moment. Boot-time registration does not, and ActionHandlerRegistry::register inserts — last writer wins — so five boot registrations would leave gr working in exactly one view and silently dead in the other four.

The fix is polymorphism rather than a central match: one handler for the shared action, owned by the mode that owns the chord (magit-core-mode owns gr), dispatching through this trait to whichever view is published for the buffer. Each view mode still owns its own refresh body — which is what mode-ownership requires — and no code branches on buffer kind.

Do not mix registration styles for one action id. Dropping an ActionHandlerRegistration unregisters by action id, so a mode that still registers action:magit-refresh from on_activate will, on deactivation, remove the boot-registered handler too and break gr everywhere. Any action reachable from more than one mode must be boot-registered exactly once and dispatched through here.

Required Methods§

Source

fn refresh(&self) -> Option<Effect>

gr — rebuild this view’s content in place.

Provided Methods§

Source

fn refresh_restoring(&self, site: HunkSite) -> Option<Effect>

MG.18d: rebuild after a mutation, then put the cursor back on the work restore describes.

Separate from Self::refresh because only a mutation has something to restore to: a bare gr leaves the cursor where the user parked it. The view resolves it because only the view knows its buffer’s shape — magit-status looks for an entry row, a diff buffer for a diff --git header.

The default is a plain refresh: a view that cannot say where the work went rebuilds and leaves the cursor alone, which is the pre-MG.18d behaviour.

Source

fn stage(&self, cursor: Position) -> Option<Effect>

s — stage the entry at cursor.

Bound by magit-status-mode and magit-diff-mode, which read their own buffer’s format to find the path (a status entry line vs. the nearest diff --git header). Views that offer no staging decline, which is what the default does — magit-log has no s chord, so its view is never asked.

Source

fn stage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect>

s in Visual mode — stage every entry the selection covers.

None (the default) means this view has no range answer and the caller falls back to the single-entry path at the cursor.

One call, not one per row. Iterating Self::stage over the selection would spawn a git process and a buffer refresh per file, and those refreshes race each other — the last to land wins, so the buffer can end up showing a state from the middle of the batch. A view that answers this stages the whole set in one task and refreshes once.

Source

fn unstage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect>

u in Visual mode. Peer of Self::stage_rows.

Source

fn unstage(&self, cursor: Position) -> Option<Effect>

u — unstage the entry at cursor. Peer of Self::stage.

Source

fn diff_source(&self, cursor: Position) -> Option<DiffSource>

MG.18c: which diff the text at cursor came from.

Why staging has to ask. A hunk’s patch is only meaningful against the tree it was diffed from: an unstaged hunk applies forward into the index (s), a staged one reverses back out of it (u). Pressing the wrong one produces a patch git refuses, which reaches the user as error: patch does not apply — indistinguishable from a missed keypress. Worse, x on a staged hunk would reverse it out of the worktree while leaving it in the index: the change vanishes from the file but is still committed by the next cc.

So the operation asks first and declines with a sentence. Every view answers from what it already knows — magit-status from the section header above the cursor, magit-diff from the scope in its buffer name.

None means “not classifiable here”, and hunk-level staging is refused rather than guessed: *magit:diff* (against HEAD) mixes both sides in one hunk, and a commit’s or stash’s inline patch in magit-status belongs to neither tree. File-level staging is unaffected — it never needed this answer.

Source

fn commit_at_cursor(&self, cursor: Position) -> Option<String>

MG.20: the commit this view describes at cursor, if any.

Reset, revert and cherry-pick all mean “act on the commit under the cursor”, and every view that shows commits answers that question differently — a log row, a --stat header, a rebase todo line, a Recent-commits entry. Rather than a handler per view (which the shared-action collision in MG.13 showed does not work) or a match buffer_kind in the host (which the everything-is-a-buffer rule forbids), each view answers here and magit-core-mode owns one handler per operation.

Views with no commits decline, which is what the default does.

Source

fn stash_at_cursor(&self, cursor: Position) -> Option<usize>

The stash index this view describes at cursor, if any.

The peer of Self::commit_at_cursor, and it exists for the same reason: apply / pop / drop / show all mean “act on the stash under the cursor”, and more than one view shows stashes. The stash-LIST buffer is the obvious one, but magit-status has a Stashes section rendering byte-identical rows, and before this the handlers resolved through StashState — so every stash chord was dead in the status buffer, and the dispatch menu’s stash rows silently did nothing anywhere.

Views with no stashes decline, which is what the default does. A view that has them parses its own row format, exactly as it does for commits.

Source

fn diff_target(&self, path: &Path, cursor: Position) -> Option<Effect>

MG.22: which version of path <CR> should open, for a cursor sitting in this view’s diff content.

The split is the point. Finding the path is diff-text parsing and identical everywhere, so it belongs to magit-hunk-mode (crate::hunk::path_at_cursor) — three modes had a copy, and one of them had a bug the other two did not. Choosing the version is genuinely per-view and cannot be shared:

View<CR> opens
magit-diff, staged scopethe index blob
magit-diff, unstaged / HEAD scopethe working-tree file
magit-committhe index blob (its diff IS the index)
magit-revisionthe file at that sha
magit-stash-showthe file as the stash left it

None means “this view has no answer for that path”, and the caller says so rather than guessing at a version — opening the working-tree copy when the user asked for a historical one is the mistake magit-file-revision-mode exists to prevent. MG.50: cursor came with this in MG.50 because magit-status needs it — which version of a file its inline diff describes is a property of the SECTION the cursor sits under (Staged vs Unstaged), not of the buffer. The views whose whole buffer has one scope ignore it.

Source

fn visit_at_cursor(&self, cursor: Position) -> Option<Effect>

MG.22: what <CR> does when the cursor is not in diff content.

Only magit-status needs this, and it is why <CR> could not simply move to magit-hunk-mode wholesale: there the chord is context-aware over rows that are not diffs at all — a file entry, a stash, a commit — and a minor’s binding wins over a major’s, so taking the chord without carrying that behaviour would have silently replaced it with a diff-only handler.

Views whose buffer is entirely diff content never reach this.

Source

fn workdir(&self) -> Option<PathBuf>

The workdir this view’s repository lives in — needed to run an operation against it from a handler that holds only the view.

Source

fn file_lines( &self, store: &BufferStoreHandle, buffer: BufferId, ) -> Option<Vec<u32>>

The rows ]f / [f treat as “a file”, when this view’s answer differs from magit-status’s.

None (the default) means the generic scan — indented entry rows, which is magit-status’s shape and was the ONLY shape until now. In a buffer whose content is a unified diff that scan is not merely useless, it is wrong: every indented context line starts with two spaces too, so ]f walked through arbitrary lines of code while claiming to move between files. The same class of bug MG.24a found in ]c / [c, which were bound universally and dead in the six majors with no hunks.

Views whose content is a diff return the diff --git header rows instead.

The store and buffer are passed in rather than read off the view: two of the diff views keep no store in their state, and adding one just to answer this would be state carried for the caller’s convenience.

Source

fn argument_flags(&self) -> &'static [RemoteFlag]

MG.23k: the git arguments this view can be re-run with — the rows D offers.

Empty (the default) means the view takes no arguments, and D says so rather than opening a menu with nothing in it.

Why this is one chord and not magit’s two. Magit binds D for diff arguments and L for log arguments. D is an editing operator and therefore inert in a read-only magit buffer, so it carries over unchanged — but L is the bottom-of-screen motion, the same class as M and B that stay off chords entirely. Rather than invent a second key, D asks the view what arguments it has: the polymorphism this trait already provides for gr is exactly the same shape.

Source

fn refresh_with_args(&self, extra: Vec<String>) -> Option<Effect>

Re-run this view with extra appended to its git invocation.

The values come from the D menu, so they REPLACE whatever the last run used rather than accumulating — the menu always opens with its toggles clear, and “what the menu shows is what runs” is the only reading that stays true after a refresh.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§