feat: dim whitespace markers and configure scope and newlines
This commit is contained in:
+110
-8
@@ -47,6 +47,7 @@ pub struct Config {
|
||||
pub scroll_distance: Option<usize>,
|
||||
pub highlight_trailing_whitespace: bool,
|
||||
pub whitespace_characters: BTreeMap<char, char>,
|
||||
pub whitespace_scope: crate::editor::WhitespaceScope,
|
||||
pub diff_context_lines: usize,
|
||||
pub diff_expand_lines: usize,
|
||||
pub diff_view_mode: crate::diff::ViewMode,
|
||||
@@ -95,6 +96,7 @@ impl Default for Config {
|
||||
scroll_distance: Some(0),
|
||||
highlight_trailing_whitespace: false,
|
||||
whitespace_characters: BTreeMap::new(),
|
||||
whitespace_scope: crate::editor::WhitespaceScope::All,
|
||||
diff_context_lines: 10,
|
||||
diff_expand_lines: 15,
|
||||
diff_view_mode: crate::diff::ViewMode::Split,
|
||||
@@ -130,6 +132,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
|
||||
scroll_distance,
|
||||
highlight_trailing_whitespace,
|
||||
whitespace_characters,
|
||||
whitespace_scope,
|
||||
diff_context_lines,
|
||||
diff_expand_lines,
|
||||
diff_view_mode,
|
||||
@@ -166,6 +169,7 @@ pub fn load_config(path: &Path) -> Result<Config, String> {
|
||||
config.scroll_distance = scroll_distance;
|
||||
config.highlight_trailing_whitespace = highlight_trailing_whitespace;
|
||||
config.whitespace_characters = whitespace_characters;
|
||||
config.whitespace_scope = whitespace_scope;
|
||||
config.diff_context_lines = diff_context_lines;
|
||||
config.diff_expand_lines = diff_expand_lines;
|
||||
config.diff_view_mode = diff_view_mode;
|
||||
@@ -502,6 +506,7 @@ struct ConfigFrame {
|
||||
highlight_trailing_whitespace: bool,
|
||||
#[serde(deserialize_with = "whitespace_characters")]
|
||||
whitespace_characters: BTreeMap<char, char>,
|
||||
whitespace_scope: crate::editor::WhitespaceScope,
|
||||
#[serde(deserialize_with = "diff_context_lines")]
|
||||
diff_context_lines: usize,
|
||||
#[serde(deserialize_with = "diff_expand_lines")]
|
||||
@@ -519,11 +524,11 @@ fn whitespace_characters<'de, D: serde::Deserializer<'de>>(
|
||||
let characters = BTreeMap::<char, char>::deserialize(deserializer)?;
|
||||
for (&whitespace, &marker) in &characters {
|
||||
if !whitespace.is_whitespace()
|
||||
|| (whitespace != '\t'
|
||||
|| (!matches!(whitespace, '\t' | '\n')
|
||||
&& (whitespace.is_control() || whitespace.width().unwrap_or(0) == 0))
|
||||
{
|
||||
return Err(serde::de::Error::custom(
|
||||
"whitespace_characters keys must be visible-width whitespace characters or tabs",
|
||||
"whitespace_characters keys must be visible-width whitespace characters, tabs, or newlines",
|
||||
));
|
||||
}
|
||||
if marker.is_control() || marker.width() != Some(1) {
|
||||
@@ -1269,9 +1274,9 @@ 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","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
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","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
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","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
r#"{"type":"config","version":1,"plugins":[],"lsp":{},"bindings":[],"file_explorer_side":"right","file_picker_view":"list","file_explorer_view":"tree","clipboard_mode":"system","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"whitespace_scope":"all","top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
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","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"whitespace_scope":"all","top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
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","git_blame":true,"git_gutter":"bar","scroll_distance":0,"highlight_trailing_whitespace":false,"whitespace_characters":{},"whitespace_scope":"all","top_bar":{},"bottom_bar":{},"popup":{"command":{},"search":{}},"labels":{},"diff_context_lines":10,"diff_expand_lines":15,"diff_view_mode":"split","diff_split_orientation":"vertical","diff_follow_working_copy":true}"#,
|
||||
] {
|
||||
assert!(serde_json::from_str::<Response>(frame).is_err(), "{frame}");
|
||||
}
|
||||
@@ -1350,6 +1355,7 @@ end
|
||||
"scroll_distance": 0,
|
||||
"highlight_trailing_whitespace": false,
|
||||
"whitespace_characters": {},
|
||||
"whitespace_scope": "all",
|
||||
"top_bar": {}, "bottom_bar": {}, "popup": {"command": {}, "search": {}},
|
||||
"labels": {}, "diff_context_lines": 10, "diff_expand_lines": 15,
|
||||
"diff_view_mode": "split", "diff_split_orientation": "vertical",
|
||||
@@ -1397,10 +1403,13 @@ end
|
||||
r#"{ " " => "." }"#,
|
||||
r#"{ " " => ".." }"#,
|
||||
r#"{ "x" => "." }"#,
|
||||
r#"{ "\n" => "." }"#,
|
||||
r#"{ "\u000b" => "." }"#,
|
||||
r#"{ "\r" => "." }"#,
|
||||
r#"{ "\u000c" => "." }"#,
|
||||
r#"{ "\u0085" => "." }"#,
|
||||
r#"{ "\u200b" => "." }"#,
|
||||
r#"{ " " => "\t" }"#,
|
||||
r#"{ " " => "\n" }"#,
|
||||
r#"{ " " => "\u0301" }"#,
|
||||
r#"{ " " => "界" }"#,
|
||||
] {
|
||||
@@ -1412,14 +1421,104 @@ end
|
||||
}
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
r#"Vamp.configure { |c| c.whitespace_characters = { " " => "·", "\t" => "→", "\u00a0" => "⍽", "\u3000" => "_" } }"#,
|
||||
r#"Vamp.configure { |c| c.whitespace_characters = { " " => "·", "\t" => "→", "\n" => "↵", "\u00a0" => "⍽", "\u3000" => "_" } }"#,
|
||||
);
|
||||
assert_eq!(
|
||||
load_config(&path).unwrap().whitespace_characters,
|
||||
BTreeMap::from([(' ', '·'), ('\t', '→'), ('\u{a0}', '⍽'), ('\u{3000}', '_')]),
|
||||
BTreeMap::from([
|
||||
(' ', '·'),
|
||||
('\t', '→'),
|
||||
('\n', '↵'),
|
||||
('\u{a0}', '⍽'),
|
||||
('\u{3000}', '_'),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace_scope_validates_ruby_atomically_and_requires_valid_wire_values() {
|
||||
use crate::editor::WhitespaceScope;
|
||||
|
||||
let fixture = Fixture::new();
|
||||
let path = fixture.file("config.rb", "Vamp.configure {}");
|
||||
assert_eq!(
|
||||
load_config(&path).unwrap().whitespace_scope,
|
||||
WhitespaceScope::All
|
||||
);
|
||||
for (value, expected) in [
|
||||
("all", WhitespaceScope::All),
|
||||
("leading", WhitespaceScope::Leading),
|
||||
] {
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!(r#"Vamp.configure {{ |c| c.whitespace_scope = "{value}" }}"#),
|
||||
);
|
||||
assert_eq!(load_config(&path).unwrap().whitespace_scope, expected);
|
||||
}
|
||||
for value in [
|
||||
"nil",
|
||||
"false",
|
||||
"0",
|
||||
"[]",
|
||||
"{}",
|
||||
":all",
|
||||
r#""""#,
|
||||
r#""unknown""#,
|
||||
] {
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!("Vamp.configure {{ |c| c.whitespace_scope = {value} }}"),
|
||||
);
|
||||
assert!(load_config(&path).is_err(), "{value}");
|
||||
let path = fixture.file(
|
||||
"config.rb",
|
||||
&format!(
|
||||
r#"Vamp.configure do |c|
|
||||
c.whitespace_scope = "leading"
|
||||
begin
|
||||
c.whitespace_scope = {value}
|
||||
raise "invalid scope accepted"
|
||||
rescue TypeError, ArgumentError
|
||||
end
|
||||
end"#,
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
load_config(&path).unwrap().whitespace_scope,
|
||||
WhitespaceScope::Leading
|
||||
);
|
||||
}
|
||||
let mut frame = serde_json::json!({
|
||||
"type": "config", "version": 1, "plugins": [], "lsp": {}, "bindings": [],
|
||||
"leader": "space", "file_explorer_side": "right", "file_picker_view": "tree",
|
||||
"file_explorer_view": "tree", "clipboard_mode": "system", "git_blame": true,
|
||||
"git_gutter": "bar", "scroll_distance": 0, "highlight_trailing_whitespace": false,
|
||||
"whitespace_characters": {}, "whitespace_scope": "all",
|
||||
"top_bar": {}, "bottom_bar": {}, "popup": {"command": {}, "search": {}},
|
||||
"labels": {}, "diff_context_lines": 10, "diff_expand_lines": 15,
|
||||
"diff_view_mode": "split", "diff_split_orientation": "vertical",
|
||||
"diff_follow_working_copy": true
|
||||
});
|
||||
for value in ["all", "leading"] {
|
||||
frame["whitespace_scope"] = serde_json::json!(value);
|
||||
assert!(serde_json::from_value::<Response>(frame.clone()).is_ok());
|
||||
}
|
||||
for value in [
|
||||
serde_json::Value::Null,
|
||||
serde_json::json!(false),
|
||||
serde_json::json!(0),
|
||||
serde_json::json!([]),
|
||||
serde_json::json!({}),
|
||||
serde_json::json!(""),
|
||||
serde_json::json!("unknown"),
|
||||
] {
|
||||
frame["whitespace_scope"] = value;
|
||||
assert!(serde_json::from_value::<Response>(frame.clone()).is_err());
|
||||
}
|
||||
frame.as_object_mut().unwrap().remove("whitespace_scope");
|
||||
assert!(serde_json::from_value::<Response>(frame).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_diff_tracking_rejects_non_boolean_configuration() {
|
||||
let fixture = Fixture::new();
|
||||
@@ -1655,6 +1754,7 @@ end
|
||||
"scroll_distance": 0,
|
||||
"highlight_trailing_whitespace": false,
|
||||
"whitespace_characters": {},
|
||||
"whitespace_scope": "all",
|
||||
"popup": {"command": {}, "search": {}},
|
||||
"clipboard_mode": "system", "diff_context_lines": 0, "diff_expand_lines": 7,
|
||||
"diff_view_mode": "split", "diff_split_orientation": "vertical",
|
||||
@@ -1705,6 +1805,7 @@ end
|
||||
"clipboard_mode": "system", "git_blame": true, "git_gutter": "bar",
|
||||
"highlight_trailing_whitespace": false,
|
||||
"whitespace_characters": {},
|
||||
"whitespace_scope": "all",
|
||||
"top_bar": {}, "bottom_bar": {}, "labels": {},
|
||||
"popup": {"command": {}, "search": {}},
|
||||
"diff_context_lines": 10, "diff_expand_lines": 15,
|
||||
@@ -2135,6 +2236,7 @@ end"#,
|
||||
"scroll_distance": 0,
|
||||
"highlight_trailing_whitespace": false,
|
||||
"whitespace_characters": {},
|
||||
"whitespace_scope": "all",
|
||||
"top_bar": {}, "bottom_bar": {}, "labels": {},
|
||||
"popup": {"command": {}, "search": {}},
|
||||
"clipboard_mode": "system", "diff_context_lines": 10, "diff_expand_lines": 15,
|
||||
|
||||
Reference in New Issue
Block a user