style: shorten default bar fade decorations

This commit is contained in:
2026-10-12 22:09:32 +02:00
parent 1cd56cf3f6
commit bb5c247ab2
6 changed files with 36 additions and 25 deletions
+5 -5
View File
@@ -57,16 +57,16 @@ Vamp.configure do |config|
config.bar.top = { config.bar.top = {
left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer], left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer],
center: [], right: [:language, :lsp, :position], center: [], right: [:language, :lsp, :position],
mode: { padding: 1, leading_edge: "", trailing_edge: "▓▒░" }, mode: { padding: 1, leading_edge: "", trailing_edge: "▓░" },
position: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } position: { padding: 1, leading_edge: "░▓", trailing_edge: "" }
} }
config.bar.bottom = { config.bar.bottom = {
left: [:tabs], center: [], right: [:battery], left: [:tabs], center: [], right: [:battery],
tabs: { tabs: {
title: "{index} {view}{name}{modified}", padding: 1, title: "{index} {view}{name}{modified}", padding: 1,
leading_edge: "░▒▓", trailing_edge: "▓▒░", first_leading_edge: "" leading_edge: "░▓", trailing_edge: "▓░", first_leading_edge: ""
}, },
battery: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } battery: { padding: 1, leading_edge: "░▓", trailing_edge: "" }
} }
end end
``` ```
@@ -81,7 +81,7 @@ Each segment's `title` is a literal template supporting only `{index}`, `{view}`
`:lsp` displays only ready servers assigned to the focused buffer, in configured stack order joined with ` + `. Disabled, initializing, failed, or unassigned servers do not appear. Names come from the initialization reply's `serverInfo.name`, falling back to the configured executable basename; no arbitrary named label can make an inactive server appear active. `:lsp` displays only ready servers assigned to the focused buffer, in configured stack order joined with ` + `. Disabled, initializing, failed, or unassigned servers do not appear. Names come from the initialization reply's `serverInfo.name`, falling back to the configured executable basename; no arbitrary named label can make an inactive server appear active.
`padding` adds spaces inside the segment's styling, on both sides of its title, and accepts integers from `0` through `64` (default `1`). `leading_edge` and `trailing_edge` are separate styled glyph strings, not padding; `""` removes an edge. Tabs default to `"░▒▓"` / `"▓▒░"` edges; mode has only the trailing `"▓▒░"` edge, position and battery only the leading `"░▒▓"` edge, and other segments have none. `first_leading_edge` optionally overrides the first occurrence's leading glyphs: for tabs this is the actual first tab, not the first tab remaining after clipping; other segments have one occurrence. Tabs default this override to `""`, suppressing the first tab's left fade. Unicode widths are measured in terminal cells. Edge strings must be single-line and contain no control characters. Unknown style keys, invalid templates, and wrong types reject configuration. `padding` adds spaces inside the segment's styling, on both sides of its title, and accepts integers from `0` through `64` (default `1`). `leading_edge` and `trailing_edge` are separate styled glyph strings, not padding; `""` removes an edge. Tabs default to `"░▓"` / `"▓░"` edges; mode has only the trailing `"▓░"` edge, position and battery only the leading `"░▓"` edge, and other segments have none. `first_leading_edge` optionally overrides the first occurrence's leading glyphs: for tabs this is the actual first tab, not the first tab remaining after clipping; other segments have one occurrence. Tabs default this override to `""`, suppressing the first tab's left fade. Unicode widths are measured in terminal cells. Edge strings must be single-line and contain no control characters. Unknown style keys, invalid templates, and wrong types reject configuration.
Any other segment name is a custom named value, such as `:build_status`. Set initial strings with `config.labels`; an empty value hides the custom segment, including its title override, padding, and edges. Names match `[A-Za-z_][A-Za-z0-9_]*`. Labels are plain text: control characters are replaced rather than interpreted as terminal escapes. The same named value can appear in either bar. Any other segment name is a custom named value, such as `:build_status`. Set initial strings with `config.labels`; an empty value hides the custom segment, including its title override, padding, and edges. Names match `[A-Za-z_][A-Za-z0-9_]*`. Labels are plain text: control characters are replaced rather than interpreted as terminal escapes. The same named value can appear in either bar.
+1 -1
View File
@@ -233,7 +233,7 @@ Use `:tab.new` to open the current buffer in a new, focused tab with one pane. E
| `:tab.close` | `config.core.tab.close` | Close the current tab and all its panes, protecting unsaved changes | | `:tab.close` | `config.core.tab.close` | Close the current tab and all its panes, protecting unsaved changes |
| `:tab.force_close` | `config.core.tab.force_close` | Explicitly discard exclusively displayed edits and close the tab | | `:tab.force_close` | `config.core.tab.force_close` | Explicitly discard exclusively displayed edits and close the tab |
By default, multiple tabs show a single top row with numbered filenames and dirty markers, with padding inside each tab's styling. The active tab has an accent-colored background fading into the terminal background through `░▒▓` / `▓▒░`, except the first tab has no left fade; inactive tabs remain unfilled. Narrow terminals keep the active title visible. New/close actions have no default keybinding; for example, `config.bind "leader t", config.core.tab.new`. By default, multiple tabs show a single top row with numbered filenames and dirty markers, with padding inside each tab's styling. The active tab has an accent-colored background fading into the terminal background through `░▓` / `▓░`, except the first tab has no left fade; inactive tabs remain unfilled. Narrow terminals keep the active title visible. New/close actions have no default keybinding; for example, `config.bind "leader t", config.core.tab.new`.
The [top and bottom bars](configuration.md#top-and-bottom-bars) accept the same builtin and custom segments, anchored left, truly centered on the full row, or anchored right. Either bar can show tabs or status information, with independent title templates, internal padding, and styled edge glyphs. Defaults keep tabs and battery above, with home-abbreviated paths on the bottom left and language/ready LSP names/position on the bottom right. Floating input and operation notifications overlay the workspace without reserving a footer. Empty content consumes no row, and a bar whose only visible content is tabs is hidden with a single tab in either position. Custom named values are session-wide and can update through plugins without editing buffers; empty values hide their segments. Both bars share collision handling: narrow layouts first omit inactive tabs, then optional segments from group ends to avoid overlap; the active tab drops decorations before its title is clipped. The [top and bottom bars](configuration.md#top-and-bottom-bars) accept the same builtin and custom segments, anchored left, truly centered on the full row, or anchored right. Either bar can show tabs or status information, with independent title templates, internal padding, and styled edge glyphs. Defaults keep tabs and battery above, with home-abbreviated paths on the bottom left and language/ready LSP names/position on the bottom right. Floating input and operation notifications overlay the workspace without reserving a footer. Empty content consumes no row, and a bar whose only visible content is tabs is hidden with a single tab in either position. Custom named values are session-wide and can update through plugins without editing buffers; empty values hide their segments. Both bars share collision handling: narrow layouts first omit inactive tabs, then optional segments from group ends to avoid overlap; the active tab drops decorations before its title is clipped.
+4 -4
View File
@@ -13,16 +13,16 @@ Vamp.configure do |config|
config.bar.top = { config.bar.top = {
left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer], left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer],
center: [], right: [:language, :lsp, :position], center: [], right: [:language, :lsp, :position],
mode: { padding: 1, leading_edge: "", trailing_edge: "▓▒░" }, mode: { padding: 1, leading_edge: "", trailing_edge: "▓░" },
position: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } position: { padding: 1, leading_edge: "░▓", trailing_edge: "" }
} }
config.bar.bottom = { config.bar.bottom = {
left: [:tabs], center: [], right: [:battery], left: [:tabs], center: [], right: [:battery],
tabs: { tabs: {
title: "{index} {view}{name}{modified}", padding: 1, title: "{index} {view}{name}{modified}", padding: 1,
leading_edge: "░▒▓", trailing_edge: "▓▒░", first_leading_edge: "" leading_edge: "░▓", trailing_edge: "▓░", first_leading_edge: ""
}, },
battery: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } battery: { padding: 1, leading_edge: "░▓", trailing_edge: "" }
} }
config.bind "ctrl+j", config.core.down, mode: config.modes.selection config.bind "ctrl+j", config.core.down, mode: config.modes.selection
config.bind "ctrl+k", config.core.up, mode: config.modes.selection config.bind "ctrl+k", config.core.up, mode: config.modes.selection
+2 -3
View File
@@ -199,10 +199,10 @@ impl ChromeLine {
fn default_segment(name: &str) -> SegmentConfig { fn default_segment(name: &str) -> SegmentConfig {
let mut segment = SegmentConfig::default(); let mut segment = SegmentConfig::default();
if matches!(name, "tabs" | "position" | "battery") { if matches!(name, "tabs" | "position" | "battery") {
segment.leading_edge = "░▒▓".into(); segment.leading_edge = "░▓".into();
} }
if matches!(name, "tabs" | "mode") { if matches!(name, "tabs" | "mode") {
segment.trailing_edge = "▓▒░".into(); segment.trailing_edge = "▓░".into();
} }
if name == "tabs" { if name == "tabs" {
segment.title = "{index} {view}{name}{modified}".into(); segment.title = "{index} {view}{name}{modified}".into();
@@ -430,7 +430,6 @@ mod tests {
"bottom": {"left": [], "center": ["workspace"]} "bottom": {"left": [], "center": ["workspace"]}
})).unwrap(); })).unwrap();
assert_eq!(lines.top.left, ["tabs"]); assert_eq!(lines.top.left, ["tabs"]);
assert_eq!(lines.top.segment("tabs").leading_edge, "░▒▓");
assert_eq!(lines.top.segment("battery").padding, 0); assert_eq!(lines.top.segment("battery").padding, 0);
assert!(lines.bottom.left.is_empty()); assert!(lines.bottom.left.is_empty());
assert_eq!(lines.bottom.right, ["language", "lsp", "position"]); assert_eq!(lines.bottom.right, ["language", "lsp", "position"]);
-1
View File
@@ -1220,7 +1220,6 @@ end
"T{index}:{path}{modified}" "T{index}:{path}{modified}"
); );
assert_eq!(loaded.top_bar.segment("tabs").padding, 0); assert_eq!(loaded.top_bar.segment("tabs").padding, 0);
assert_eq!(loaded.top_bar.segment("tabs").trailing_edge, "▓▒░");
assert_eq!(loaded.top_bar.segment("battery").leading_edge, "界"); assert_eq!(loaded.top_bar.segment("battery").leading_edge, "界");
assert_eq!( assert_eq!(
loaded.top_bar.segment("workspace"), loaded.top_bar.segment("workspace"),
+24 -11
View File
@@ -7145,7 +7145,7 @@ mod tests {
.draw(|frame| views.draw(frame, &mut editor)) .draw(|frame| views.draw(frame, &mut editor))
.unwrap(); .unwrap();
let title = pane_screen(&terminal, Rect::new(0, 0, 80, 1)); let title = pane_screen(&terminal, Rect::new(0, 0, 80, 1));
assert!(title.ends_with("░▒▓ bat 27% "), "{title}"); assert!(title.trim_end().ends_with("bat 27%"), "{title}");
assert_eq!(views.document.geometry.unwrap().1.y, 1); assert_eq!(views.document.geometry.unwrap().1.y, 1);
editor editor
@@ -7159,11 +7159,7 @@ mod tests {
assert!(title.contains("電") && title.contains("池"), "{title}"); assert!(title.contains("電") && title.contains("池"), "{title}");
assert!(title.contains("e\u{301}�90%"), "{title}"); assert!(title.contains("e\u{301}�90%"), "{title}");
assert!(!title.contains("bat 27%"), "{title}"); assert!(!title.contains("bat 27%"), "{title}");
let start = 80 - (editor.label("battery").width() + 5) as u16;
let buffer = terminal.backend().buffer(); let buffer = terminal.backend().buffer();
for (offset, glyph) in [(0, "░"), (1, "▒"), (2, "▓"), (3, " ")] {
assert_eq!(buffer[(start + offset, 0)].symbol(), glyph);
}
assert_eq!(buffer[(78, 0)].symbol(), "%"); assert_eq!(buffer[(78, 0)].symbol(), "%");
assert_eq!(buffer[(79, 0)].symbol(), " "); assert_eq!(buffer[(79, 0)].symbol(), " ");
assert_eq!(views.document.geometry.unwrap().1.y, 1); assert_eq!(views.document.geometry.unwrap().1.y, 1);
@@ -7175,14 +7171,18 @@ mod tests {
terminal terminal
.draw(|frame| views.draw(frame, &mut editor)) .draw(|frame| views.draw(frame, &mut editor))
.unwrap(); .unwrap();
assert!(pane_screen(&terminal, Rect::new(0, 0, 80, 1)).ends_with("░▒▓ live ")); assert!(
pane_screen(&terminal, Rect::new(0, 0, 80, 1))
.trim_end()
.ends_with("live")
);
assert!(!screen_text(&terminal).contains("90%")); assert!(!screen_text(&terminal).contains("90%"));
editor.set_label("battery".into(), String::new()).unwrap(); editor.set_label("battery".into(), String::new()).unwrap();
terminal terminal
.draw(|frame| views.draw(frame, &mut editor)) .draw(|frame| views.draw(frame, &mut editor))
.unwrap(); .unwrap();
let title = pane_screen(&terminal, Rect::new(0, 0, 80, 1)); let title = pane_screen(&terminal, Rect::new(0, 0, 80, 1));
assert!(title.ends_with("░▒▓ bat 28% "), "{title}"); assert!(title.trim_end().ends_with("bat 28%"), "{title}");
assert!(!title.contains("live"), "{title}"); assert!(!title.contains("live"), "{title}");
assert_eq!(views.document.geometry.unwrap().1.y, 1); assert_eq!(views.document.geometry.unwrap().1.y, 1);
assert!(editor.set_battery_charge(None)); assert!(editor.set_battery_charge(None));
@@ -7208,6 +7208,13 @@ mod tests {
let line = if at_top { top } else { bottom }; let line = if at_top { top } else { bottom };
[&mut line.left, &mut line.center, &mut line.right][anchor] [&mut line.left, &mut line.center, &mut line.right][anchor]
.push("battery".into()); .push("battery".into());
line.segments.insert(
"battery".into(),
SegmentConfig {
padding: 0,
..SegmentConfig::default()
},
);
}); });
let mut terminal = let mut terminal =
Terminal::new(ratatui::backend::TestBackend::new(48, 6)).unwrap(); Terminal::new(ratatui::backend::TestBackend::new(48, 6)).unwrap();
@@ -7218,9 +7225,9 @@ mod tests {
.unwrap(); .unwrap();
let y = if at_top { 0 } else { 5 }; let y = if at_top { 0 } else { 5 };
let row = pane_screen(&terminal, Rect::new(0, y, 48, 1)); let row = pane_screen(&terminal, Rect::new(0, y, 48, 1));
assert_eq!(row.trim(), "░▒▓ bat 27%"); assert_eq!(row.trim(), "bat 27%");
let x = [0, 18, 36][anchor]; let x = [0, 20, 41][anchor];
assert_eq!(terminal.backend().buffer()[(x, y)].symbol(), "░"); assert_eq!(terminal.backend().buffer()[(x, y)].symbol(), "b");
} }
} }
configure_chrome(&mut editor, |top, bottom| { configure_chrome(&mut editor, |top, bottom| {
@@ -7251,6 +7258,12 @@ mod tests {
"edit {}", "edit {}",
directory.file("active.txt", "ACTIVE").display() directory.file("active.txt", "ACTIVE").display()
)); ));
configure_chrome(&mut editor, |top, _| {
let tabs = top.segments.get_mut("tabs").unwrap();
tabs.leading_edge = "<<<".into();
tabs.trailing_edge = ">>>".into();
top.segments.get_mut("battery").unwrap().leading_edge = "<<<".into();
});
editor.set_label("battery".into(), "90%".into()).unwrap(); editor.set_label("battery".into(), "90%".into()).unwrap();
for width in [28, 29, 80] { for width in [28, 29, 80] {
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(width, 2)).unwrap(); let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(width, 2)).unwrap();
@@ -7269,7 +7282,7 @@ mod tests {
if width == 28 { if width == 28 {
assert!(!title.contains("90%"), "{title}"); assert!(!title.contains("90%"), "{title}");
} else { } else {
assert!(title.ends_with(" ░▒▓ 90% "), "{title}"); assert!(title.ends_with(" <<< 90% "), "{title}");
} }
} }
editor editor