diff --git a/docs/configuration.md b/docs/configuration.md index aac7eab..c232036 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -57,16 +57,16 @@ Vamp.configure do |config| config.bar.top = { left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer], center: [], right: [:language, :lsp, :position], - mode: { padding: 1, leading_edge: "", trailing_edge: "▓▒░" }, - position: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } + mode: { padding: 1, leading_edge: "", trailing_edge: "▓░" }, + position: { padding: 1, leading_edge: "░▓", trailing_edge: "" } } config.bar.bottom = { left: [:tabs], center: [], right: [:battery], tabs: { 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 ``` @@ -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. -`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. diff --git a/docs/editing.md b/docs/editing.md index f2f62aa..30cf2da 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -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.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. diff --git a/examples/config.rb b/examples/config.rb index b580e3c..22d2454 100644 --- a/examples/config.rb +++ b/examples/config.rb @@ -13,16 +13,16 @@ Vamp.configure do |config| config.bar.top = { left: [:mode, :path, :diagnostics, :selections, :count, :pending, :buffer], center: [], right: [:language, :lsp, :position], - mode: { padding: 1, leading_edge: "", trailing_edge: "▓▒░" }, - position: { padding: 1, leading_edge: "░▒▓", trailing_edge: "" } + mode: { padding: 1, leading_edge: "", trailing_edge: "▓░" }, + position: { padding: 1, leading_edge: "░▓", trailing_edge: "" } } config.bar.bottom = { left: [:tabs], center: [], right: [:battery], tabs: { 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+k", config.core.up, mode: config.modes.selection diff --git a/src/chrome.rs b/src/chrome.rs index cfbb446..939396d 100644 --- a/src/chrome.rs +++ b/src/chrome.rs @@ -199,10 +199,10 @@ impl ChromeLine { fn default_segment(name: &str) -> SegmentConfig { let mut segment = SegmentConfig::default(); if matches!(name, "tabs" | "position" | "battery") { - segment.leading_edge = "░▒▓".into(); + segment.leading_edge = "░▓".into(); } if matches!(name, "tabs" | "mode") { - segment.trailing_edge = "▓▒░".into(); + segment.trailing_edge = "▓░".into(); } if name == "tabs" { segment.title = "{index} {view}{name}{modified}".into(); @@ -430,7 +430,6 @@ mod tests { "bottom": {"left": [], "center": ["workspace"]} })).unwrap(); assert_eq!(lines.top.left, ["tabs"]); - assert_eq!(lines.top.segment("tabs").leading_edge, "░▒▓"); assert_eq!(lines.top.segment("battery").padding, 0); assert!(lines.bottom.left.is_empty()); assert_eq!(lines.bottom.right, ["language", "lsp", "position"]); diff --git a/src/ruby.rs b/src/ruby.rs index 785d59c..b0d2061 100644 --- a/src/ruby.rs +++ b/src/ruby.rs @@ -1220,7 +1220,6 @@ end "T{index}:{path}{modified}" ); 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("workspace"), diff --git a/src/tui.rs b/src/tui.rs index 3241e2a..5d491cb 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -7145,7 +7145,7 @@ mod tests { .draw(|frame| views.draw(frame, &mut editor)) .unwrap(); 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); editor @@ -7159,11 +7159,7 @@ mod tests { assert!(title.contains("電") && title.contains("池"), "{title}"); assert!(title.contains("e\u{301}�90%"), "{title}"); assert!(!title.contains("bat 27%"), "{title}"); - let start = 80 - (editor.label("battery").width() + 5) as u16; 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[(79, 0)].symbol(), " "); assert_eq!(views.document.geometry.unwrap().1.y, 1); @@ -7175,14 +7171,18 @@ mod tests { terminal .draw(|frame| views.draw(frame, &mut editor)) .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%")); editor.set_label("battery".into(), String::new()).unwrap(); terminal .draw(|frame| views.draw(frame, &mut editor)) .unwrap(); 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_eq!(views.document.geometry.unwrap().1.y, 1); assert!(editor.set_battery_charge(None)); @@ -7208,6 +7208,13 @@ mod tests { let line = if at_top { top } else { bottom }; [&mut line.left, &mut line.center, &mut line.right][anchor] .push("battery".into()); + line.segments.insert( + "battery".into(), + SegmentConfig { + padding: 0, + ..SegmentConfig::default() + }, + ); }); let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(48, 6)).unwrap(); @@ -7218,9 +7225,9 @@ mod tests { .unwrap(); let y = if at_top { 0 } else { 5 }; let row = pane_screen(&terminal, Rect::new(0, y, 48, 1)); - assert_eq!(row.trim(), "░▒▓ bat 27%"); - let x = [0, 18, 36][anchor]; - assert_eq!(terminal.backend().buffer()[(x, y)].symbol(), "░"); + assert_eq!(row.trim(), "bat 27%"); + let x = [0, 20, 41][anchor]; + assert_eq!(terminal.backend().buffer()[(x, y)].symbol(), "b"); } } configure_chrome(&mut editor, |top, bottom| { @@ -7251,6 +7258,12 @@ mod tests { "edit {}", 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(); for width in [28, 29, 80] { let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(width, 2)).unwrap(); @@ -7269,7 +7282,7 @@ mod tests { if width == 28 { assert!(!title.contains("90%"), "{title}"); } else { - assert!(title.ends_with(" ░▒▓ 90% "), "{title}"); + assert!(title.ends_with(" <<< 90% "), "{title}"); } } editor