feat: add expandable diff excerpts with configurable context
This commit is contained in:
+101
-4
@@ -34,6 +34,8 @@ pub struct Config {
|
||||
pub file_picker_view: String,
|
||||
pub file_explorer_view: String,
|
||||
pub clipboard_mode: String,
|
||||
pub diff_context_lines: usize,
|
||||
pub diff_expand_lines: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -68,6 +70,8 @@ impl Default for Config {
|
||||
file_picker_view: "tree".into(),
|
||||
file_explorer_view: "tree".into(),
|
||||
clipboard_mode: "system".into(),
|
||||
diff_context_lines: 10,
|
||||
diff_expand_lines: 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +92,8 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
|
||||
file_picker_view,
|
||||
file_explorer_view,
|
||||
clipboard_mode,
|
||||
diff_context_lines,
|
||||
diff_expand_lines,
|
||||
..
|
||||
} => {
|
||||
let mut config = Config::default();
|
||||
@@ -110,6 +116,8 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
|
||||
config.file_picker_view = file_picker_view;
|
||||
config.file_explorer_view = file_explorer_view;
|
||||
config.clipboard_mode = clipboard_mode;
|
||||
config.diff_context_lines = diff_context_lines;
|
||||
config.diff_expand_lines = diff_expand_lines;
|
||||
Ok(config)
|
||||
}
|
||||
Response::Error {
|
||||
@@ -372,6 +380,34 @@ struct DocumentRequest<'a> {
|
||||
event: &'a DocumentEvent,
|
||||
}
|
||||
|
||||
fn diff_line_count<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
label: &str,
|
||||
minimum: usize,
|
||||
) -> Result<usize, D::Error> {
|
||||
let value = serde_json::Value::deserialize(deserializer)?;
|
||||
value
|
||||
.as_u64()
|
||||
.and_then(|value| usize::try_from(value).ok())
|
||||
.filter(|value| *value >= minimum)
|
||||
.ok_or_else(|| {
|
||||
serde::de::Error::custom(format!(
|
||||
"{label} must be an Integer between {minimum} and {}",
|
||||
usize::MAX
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn diff_context_lines<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<usize, D::Error> {
|
||||
diff_line_count(deserializer, "diff_context_lines", 0)
|
||||
}
|
||||
|
||||
fn diff_expand_lines<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
|
||||
diff_line_count(deserializer, "diff_expand_lines", 1)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", deny_unknown_fields)]
|
||||
enum Response {
|
||||
@@ -397,6 +433,10 @@ enum Response {
|
||||
file_picker_view: String,
|
||||
file_explorer_view: String,
|
||||
clipboard_mode: String,
|
||||
#[serde(deserialize_with = "diff_context_lines")]
|
||||
diff_context_lines: usize,
|
||||
#[serde(deserialize_with = "diff_expand_lines")]
|
||||
diff_expand_lines: usize,
|
||||
},
|
||||
#[serde(rename = "result")]
|
||||
Result {
|
||||
@@ -1048,14 +1088,71 @@ end
|
||||
assert!(load_config(&path).unwrap_err().contains(error), "{value}");
|
||||
}
|
||||
for frame in [
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system"}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"leader":null,"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system"}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"leader":7,"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system"}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system","diff_context_lines":10,"diff_expand_lines":15}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"leader":null,"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system","diff_context_lines":10,"diff_expand_lines":15}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"leader":7,"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system","diff_context_lines":10,"diff_expand_lines":15}"#,
|
||||
] {
|
||||
assert!(serde_json::from_str::<Response>(frame).is_err(), "{frame}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_diff_excerpt_counts_accept_custom_values_and_reject_invalid_values() {
|
||||
let fixture = Fixture::new();
|
||||
for (context, expand) in [(0, 7), (3, 1), (usize::MAX, usize::MAX)] {
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!(
|
||||
"Vamp.configure {{ |config| config.diff_context_lines = {context}; config.diff_expand_lines = {expand} }}"
|
||||
),
|
||||
);
|
||||
let loaded = load_config(&path).unwrap();
|
||||
assert_eq!(
|
||||
(loaded.diff_context_lines, loaded.diff_expand_lines),
|
||||
(context, expand)
|
||||
);
|
||||
}
|
||||
let frame = serde_json::json!({
|
||||
"version": 1, "type": "config", "plugins": [], "lsp": {},
|
||||
"bindings": [], "leader": "space", "file_explorer_side": "right",
|
||||
"file_picker_view": "tree", "file_explorer_view": "tree",
|
||||
"clipboard_mode": "system", "diff_context_lines": 0, "diff_expand_lines": 7
|
||||
});
|
||||
for setting in ["diff_context_lines", "diff_expand_lines"] {
|
||||
for value in [
|
||||
"-1",
|
||||
"1.5",
|
||||
"\"3\"",
|
||||
"true",
|
||||
"false",
|
||||
"nil",
|
||||
"18446744073709551616",
|
||||
"0",
|
||||
] {
|
||||
if setting == "diff_context_lines" && value == "0" {
|
||||
continue;
|
||||
}
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!("Vamp.configure {{ |config| config.{setting} = {value} }}"),
|
||||
);
|
||||
let error = load_config(&path).unwrap_err();
|
||||
assert!(
|
||||
error.contains(setting) && error.contains("must be"),
|
||||
"{value}: {error}"
|
||||
);
|
||||
|
||||
let mut invalid_frame = frame.clone();
|
||||
invalid_frame[setting] =
|
||||
serde_json::from_str(if value == "nil" { "null" } else { value }).unwrap();
|
||||
let error = serde_json::from_value::<Response>(invalid_frame)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(error.to_string().contains(setting), "{value}: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_configuration_is_disabled_unless_explicitly_enabled() {
|
||||
let fixture = Fixture::new();
|
||||
@@ -1431,7 +1528,7 @@ end"#,
|
||||
"version": 1, "type": "config", "plugins": [], "lsp": {},
|
||||
"bindings": [], "leader": "space", "file_explorer_side": "right",
|
||||
"file_picker_view": "tree", "file_explorer_view": "tree",
|
||||
"clipboard_mode": "system"
|
||||
"clipboard_mode": "system", "diff_context_lines": 10, "diff_expand_lines": 15
|
||||
});
|
||||
let Response::Config { lsp, .. } =
|
||||
serde_json::from_value::<Response>(frame.clone()).unwrap()
|
||||
|
||||
Reference in New Issue
Block a user