use super::super::theme::{secondary, warning};
use super::status::approval_colour;
use crate::interactive::session_prompt::StatusView;
use ratatui::{
Frame,
layout::Rect,
style::{Color, Style},
text::{Line, Span},
widgets::Paragraph,
};
#[cfg(test)]
pub(crate) fn context_words_for_test(view: &StatusView, width: u16) -> String {
context_words(view, width as usize)
}
#[cfg(test)]
pub(crate) fn context_spans_for_test(view: &StatusView, width: u16) -> Vec<Span<'static>> {
context_spans(view, width as usize)
}
const DROP_LEAD: u8 = 0;
const DROP_WORKSPACE: u8 = 1;
const DROP_UNUSUAL: u8 = 2;
fn segments(view: &StatusView) -> Vec<(String, Color, u8)> {
let mut out = vec![
("saya".to_string(), secondary(), DROP_LEAD),
("Database read-only".to_string(), secondary(), u8::MAX),
match view.workspace_root.as_deref() {
Some(root) => (format!("Workspace {root}"), secondary(), DROP_WORKSPACE),
None => (
"No workspace bound".to_string(),
secondary(),
DROP_WORKSPACE,
),
},
];
if view.sharing_on {
out.push(("Data sharing on".to_string(), warning(), DROP_UNUSUAL));
}
if view.approval_mode != "read-only" {
out.push((
format!("Approval: {}", view.approval_mode),
approval_colour(&view.approval_mode),
DROP_UNUSUAL,
));
}
if view.host_composed {
out.push((
"Host commands unsandboxed".to_string(),
warning(),
DROP_UNUSUAL,
));
}
if !view.denied_programs.is_empty() {
out.push((
format!("Denied: {}", view.denied_programs.join(", ")),
warning(),
DROP_UNUSUAL,
));
}
out
}
fn fit(mut fitted: Vec<(String, Color, u8)>, width: usize) -> Vec<(String, Color, u8)> {
let joined_len = |fitted: &[(String, Color, u8)]| {
fitted.iter().map(|(text, _, _)| text.len()).sum::<usize>()
+ fitted.len().saturating_sub(1) * " · ".len()
};
while joined_len(&fitted) > width {
let cheapest = fitted
.iter()
.enumerate()
.filter(|(_, (_, _, rank))| *rank != u8::MAX)
.min_by_key(|(idx, (_, _, rank))| (*rank, std::cmp::Reverse(*idx)))
.map(|(idx, _)| idx);
match cheapest {
Some(idx) => {
fitted.remove(idx);
}
None => break,
}
}
fitted
}
#[cfg(test)]
fn context_words(view: &StatusView, width: usize) -> String {
let unstyled: String = context_spans(view, width)
.iter()
.map(|span| span.content.as_ref())
.collect();
unstyled
}
fn context_spans(view: &StatusView, width: usize) -> Vec<Span<'static>> {
let mut spans = Vec::new();
for (i, (text, fg, _)) in fit(segments(view), width).into_iter().enumerate() {
if i > 0 {
spans.push(Span::styled(" · ", Style::default().fg(secondary())));
}
spans.push(Span::styled(text, Style::default().fg(fg)));
}
spans
}
pub(in crate::interactive::tui) fn draw_context_line(
frame: &mut Frame<'_>,
status: &StatusView,
area: Rect,
) {
let width = area.width as usize;
frame.render_widget(
Paragraph::new(Line::from(context_spans(status, width))),
area,
);
}
#[cfg(test)]
#[path = "context_line_tests.rs"]
mod tests;