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§
Provided Methods§
Sourcefn refresh_restoring(&self, site: HunkSite) -> Option<Effect>
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.
Sourcefn stage(&self, cursor: Position) -> Option<Effect>
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.
Sourcefn stage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect>
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.
Sourcefn unstage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect>
fn unstage_rows(&self, rows: RangeInclusive<u32>) -> Option<Effect>
u in Visual mode. Peer of Self::stage_rows.
Sourcefn unstage(&self, cursor: Position) -> Option<Effect>
fn unstage(&self, cursor: Position) -> Option<Effect>
u — unstage the entry at cursor. Peer of Self::stage.
Sourcefn diff_source(&self, cursor: Position) -> Option<DiffSource>
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.
Sourcefn commit_at_cursor(&self, cursor: Position) -> Option<String>
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.
Sourcefn stash_at_cursor(&self, cursor: Position) -> Option<usize>
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.
Sourcefn diff_target(&self, path: &Path, cursor: Position) -> Option<Effect>
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 scope | the index blob |
| magit-diff, unstaged / HEAD scope | the working-tree file |
| magit-commit | the index blob (its diff IS the index) |
| magit-revision | the file at that sha |
| magit-stash-show | the 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.
Sourcefn visit_at_cursor(&self, cursor: Position) -> Option<Effect>
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.
Sourcefn workdir(&self) -> Option<PathBuf>
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.
Sourcefn file_lines(
&self,
store: &BufferStoreHandle,
buffer: BufferId,
) -> Option<Vec<u32>>
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.
Sourcefn argument_flags(&self) -> &'static [RemoteFlag]
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.
Sourcefn refresh_with_args(&self, extra: Vec<String>) -> Option<Effect>
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".