use std::collections::{btree_map::Entry, BTreeMap};
use rmux_core::formats::FormatContext;
use rmux_core::style::Style;
use rmux_core::{OptionStore, Pane, Session, Window};
use rmux_proto::OptionName;
use crate::format_runtime::RuntimeFormatContext;
use super::{apply_runtime_style_overlay, cursor_position_bytes, style_sgr_bytes, StatusGeometry};
#[path = "borders/layout.rs"]
mod layout;
use self::layout::border_layout_cells_with_geometry;
pub(super) use self::layout::content_pane_geometry;
#[cfg_attr(not(test), allow(dead_code))]
pub(super) fn border_cells(
window: &Window,
active_pane_index: u32,
inactive_style: BorderStyle,
active_style: BorderStyle,
) -> Vec<BorderCell> {
border_cells_with_geometry(
window,
active_pane_index,
inactive_style,
active_style,
StatusGeometry::without_status(window.size()),
)
}
fn border_cells_with_geometry(
window: &Window,
active_pane_index: u32,
inactive_style: BorderStyle,
active_style: BorderStyle,
geometry: StatusGeometry,
) -> Vec<BorderCell> {
border_layout_cells_with_geometry(window, active_pane_index, geometry, true)
.into_iter()
.map(|cell| BorderCell {
x: cell.x,
y: cell.y,
glyph: cell.glyph,
style: if cell.active {
active_style.clone()
} else {
inactive_style.clone()
},
})
.collect()
}
pub(super) fn runtime_border_cells(
session: &Session,
options: &OptionStore,
geometry: StatusGeometry,
) -> Vec<BorderCell> {
let window = session.window();
let window_index = session.active_window_index();
let indicators_colour = pane_border_indicators_colour_enabled(options.resolve_for_window(
session.name(),
window_index,
OptionName::PaneBorderIndicators,
));
let layout_cells = border_layout_cells_with_geometry(
window,
session.active_pane_index(),
geometry,
indicators_colour,
);
let mut style_cache = BTreeMap::<(u32, bool), BorderStyle>::new();
layout_cells
.into_iter()
.map(|cell| {
let style = cell
.owner_pane_index
.and_then(
|pane_index| match style_cache.entry((pane_index, cell.active)) {
Entry::Occupied(entry) => Some(entry.get().clone()),
Entry::Vacant(entry) => {
let pane = window.pane(pane_index)?;
Some(
entry
.insert(resolve_border_style_for_pane(
session,
options,
window_index,
pane,
cell.active,
))
.clone(),
)
}
},
)
.unwrap_or_default();
BorderCell {
x: cell.x,
y: cell.y,
glyph: cell.glyph,
style,
}
})
.collect()
}
fn pane_border_indicators_colour_enabled(value: Option<&str>) -> bool {
matches!(value, Some("colour" | "both"))
}
pub(super) fn render_cells(cells: &[BorderCell]) -> Vec<u8> {
if cells.is_empty() {
return Vec::new();
}
let mut frame = Vec::new();
let mut last_style: Option<BorderStyle> = None;
frame.extend_from_slice(b"\x1b[s");
frame.extend_from_slice(b"\x1b[0m");
for cell in cells {
frame.extend_from_slice(cursor_position_bytes(cell.y, cell.x).as_slice());
if last_style.as_ref() != Some(&cell.style) {
if last_style.is_some() {
frame.extend_from_slice(b"\x1b[0m");
}
frame.extend_from_slice(style_sgr_bytes(&cell.style, false).as_slice());
last_style = Some(cell.style.clone());
}
let mut utf8 = [0_u8; 4];
frame.extend_from_slice(cell.glyph.encode_utf8(&mut utf8).as_bytes());
}
frame.extend_from_slice(b"\x1b[0m\x1b[u");
frame
}
pub(super) type BorderStyle = Style;
fn resolve_border_style_for_pane(
session: &Session,
options: &OptionStore,
window_index: u32,
pane: &Pane,
active: bool,
) -> Style {
let context = FormatContext::from_session(session)
.with_window(window_index, session.window(), true, false)
.with_window_pane(session.window(), pane);
let runtime = RuntimeFormatContext::new(context)
.with_options(options)
.with_session(session)
.with_window(window_index, session.window())
.with_pane(pane);
let option = if active {
OptionName::PaneActiveBorderStyle
} else {
OptionName::PaneBorderStyle
};
apply_runtime_style_overlay(
&Style::default(),
options.resolve_for_pane(session.name(), window_index, pane.index(), option),
&runtime,
)
}
pub(super) struct BorderCell {
pub(super) x: u16,
pub(super) y: u16,
pub(super) glyph: char,
pub(super) style: BorderStyle,
}