Skip to main content

lattice_magit/
options.rs

1//! MG.22b: the options `magit-hunk-mode` owns — the first options
2//! `lattice-magit` registers at all.
3//!
4//! Until this slice the crate registered none, which several user-doc
5//! pages had to say out loud: every `magit.*` name a user reached for
6//! failed with `unknown option`.
7//!
8//! **`magit.hunk.syntax-highlight` landed with its feature**, as the
9//! note that used to stand here promised. It was withheld until the
10//! feature existed because an option that changes nothing is the same
11//! failure as a menu row that does nothing, just quieter — `:set`
12//! reports success either way.
13//!
14//! **`magit.hunk.line-backgrounds` is absent too, and lives elsewhere
15//! instead**: it became `ui.diff.line-backgrounds` in `lattice-diff`.
16//! MG.21a found the mechanism is generic —
17//! `Editor::diff_signs_from_spans` derives the tint from whatever spans
18//! a mode publishes, so every diff-showing buffer shares it — and an
19//! option named for one consumer would have understated what it turns
20//! off.
21
22/// Validator for `magit.hunk.context-lines`. `0` is meaningful (show
23/// only the changed lines); the ceiling stops a fat-fingered value
24/// turning every diff into the whole file.
25#[allow(clippy::ptr_arg)]
26fn validate_context_lines(n: &i64) -> Result<(), String> {
27    if *n >= 0 && *n <= 1000 {
28        Ok(())
29    } else {
30        Err(format!(
31            "magit.hunk.context-lines must be in range [0, 1000], got {n}"
32        ))
33    }
34}
35
36lattice_config::options! {
37    group = lattice_config::Magit;
38
39    /// Unchanged lines of context around each hunk in the diffs magit
40    /// generates — `git diff -U<n>`. Default `3`, which is git's own.
41    ///
42    /// **Distinct from `ui.diff.context`**, which is how much context a
43    /// *fold* leaves visible inside a two-pane diff session. This one
44    /// decides how much context git puts in the patch text to begin
45    /// with; that one decides how much of an existing diff stays
46    /// unfolded.
47    ///
48    /// `D` in a diff buffer overrides it for that view (MG.23k). The
49    /// override wins: this is the default for views that have not been
50    /// told otherwise, not a floor.
51    #[name("magit.hunk.context-lines")]
52    #[validate(validate_context_lines)]
53    pub MagitHunkContextLines: i64 = 3;
54
55    /// Syntax-highlight the code inside a diff, with the `+` / `-`
56    /// colouring layered over it. On by default.
57    ///
58    /// Turning it off gives the flat per-line colouring magit had
59    /// before — every added line one green, every removed line one red
60    /// — and skips the tree-sitter parse each hunk otherwise costs.
61    /// The parse runs off the actor thread and is bounded by the hunk,
62    /// not the file, so this is an aesthetic switch rather than a
63    /// performance one for all but the largest diffs.
64    ///
65    /// Read per refresh, not at buffer open, so `:set` takes effect on
66    /// the next `gr` rather than only on reopen — the same contract
67    /// `magit.hunk.context-lines` has.
68    #[name("magit.hunk.syntax-highlight")]
69    pub MagitHunkSyntaxHighlight: bool = true;
70
71    /// MG.54: show the file's content while choosing a revision in the
72    /// `C-c f v` picker (`:magit-find-file`). On by default.
73    ///
74    /// **This runs `git show <rev>:<path>` on the input thread**, so the
75    /// trade-off is visible here rather than discovered as jank. It is
76    /// affordable because it is *debounced*: arrowing through revisions
77    /// runs nothing at all, and the fetch happens only once the
78    /// selection has sat still for a moment — so a scroll through fifty
79    /// revisions costs one `git show`, not fifty. The cost you can feel
80    /// is a keystroke arriving while that one fetch is in flight, which
81    /// waits for it. Blobs over 256 KiB are refused with a note in the
82    /// pane instead of being fetched.
83    ///
84    /// Turn it off and the picker behaves as it did before: a list of
85    /// revisions, with the file shown only once you accept one.
86    ///
87    /// Read per selection, not at picker-open, so `:set` takes effect
88    /// without reopening the picker.
89    #[name("magit.revision-preview")]
90    pub MagitRevisionPreview: bool = true;
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn zero_context_is_allowed_and_a_silly_value_is_not() {
99        // `0` is meaningful — show only the changed lines.
100        assert!(validate_context_lines(&0).is_ok());
101        assert!(validate_context_lines(&3).is_ok());
102        assert!(validate_context_lines(&1000).is_ok());
103        assert!(validate_context_lines(&-1).is_err());
104        assert!(validate_context_lines(&1001).is_err());
105    }
106
107    /// The message has to name the option, since `:set` reports it
108    /// verbatim and "out of range" alone says nothing about which
109    /// setting was refused.
110    #[test]
111    fn the_rejection_names_the_option_and_the_value() {
112        let e = validate_context_lines(&-5).expect_err("negative is refused");
113        assert!(e.contains("magit.hunk.context-lines"), "{e}");
114        assert!(e.contains("-5"), "{e}");
115    }
116}