use {
super::*,
crate::{
command::*,
display::{
Areas,
Screen,
W,
status_line,
},
errors::ProgramError,
keys::KEY_FORMAT,
skin::PanelSkin,
task_sync::Dam,
verb::*,
},
std::sync::atomic::{
AtomicUsize,
Ordering,
},
termimad::minimad::{
Alignment,
Composite,
},
};
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
pub struct Panel {
pub id: PanelId,
states: Vec<Box<dyn PanelState>>, pub areas: Areas,
pub status: Status,
pub purpose: PanelPurpose,
pub last_raw_pattern: Option<String>,
}
impl Panel {
#[must_use]
pub fn new(
state: Box<dyn PanelState>,
areas: Areas,
con: &AppContext,
) -> Self {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed).into();
let mut input = PanelInput::new(areas.input.clone());
input.set_content(&state.get_starting_input());
let status = state.no_verb_status(false, con, areas.status.width as usize);
Self {
id,
states: vec![state],
areas,
status,
purpose: PanelPurpose::None,
last_raw_pattern: None,
}
}
pub fn set_error(
&mut self,
text: String,
) {
self.status = Status::from_error(text);
}
pub fn set_message<S: Into<String>>(
&mut self,
md: S,
) {
self.status = Status::from_message(md.into());
}
pub fn apply_command<'c>(
&mut self,
w: &'c mut W,
cmd: &'c Command,
app_state: &mut AppState,
app_cmd_context: &'c AppCmdContext<'c>,
) -> Result<CmdResult, ProgramError> {
if let Command::PatternEdit { raw, .. } = cmd {
self.last_raw_pattern = Some(raw.clone());
}
let state_idx = self.states.len() - 1;
let cc = CmdContext {
cmd,
app: app_cmd_context,
panel: PanelCmdContext {
areas: &self.areas,
purpose: self.purpose,
},
};
let result = self.states[state_idx].on_command(w, app_state, &cc);
let has_previous_state = self.states.len() > 1;
self.status = self.state().get_status(
app_state,
&cc,
has_previous_state,
self.areas.status.width as usize,
);
result
}
pub fn do_pending_task(
&mut self,
app_state: &mut AppState,
screen: Screen,
con: &AppContext,
dam: &mut Dam,
) -> Result<(), ProgramError> {
self.mut_state()
.do_pending_task(app_state, screen, con, dam)
}
#[must_use]
pub fn has_pending_task(&self) -> bool {
self.state().get_pending_task().is_some()
}
pub fn push_state(
&mut self,
new_state: Box<dyn PanelState>,
) {
self.states.push(new_state);
}
#[must_use]
pub fn mut_state(&mut self) -> &mut dyn PanelState {
#[expect(
clippy::missing_panics_doc,
reason = "there's always at least one state"
)]
self.states.last_mut().unwrap().as_mut()
}
#[must_use]
pub fn state(&self) -> &dyn PanelState {
#[expect(
clippy::missing_panics_doc,
reason = "there's always at least one state"
)]
self.states.last().unwrap().as_ref()
}
pub fn remove_state(&mut self) -> bool {
if self.states.len() > 1 {
self.states.pop();
true
} else {
false
}
}
pub fn write_status(
&self,
w: &mut W,
watching: bool,
panel_skin: &PanelSkin,
screen: Screen,
) -> Result<(), ProgramError> {
let task = self.state().get_pending_task();
status_line::write(
w,
watching,
task,
&self.status,
&self.areas.status,
panel_skin,
screen,
)
}
pub fn write_purpose(
&self,
w: &mut W,
panel_skin: &PanelSkin,
screen: Screen,
con: &AppContext,
) -> Result<(), ProgramError> {
if !self.purpose.is_arg_edition() {
return Ok(());
}
if let Some(area) = &self.areas.purpose {
let shortcut = con
.verb_store
.verbs()
.iter()
.filter(|v| match &v.execution {
VerbExecution::Internal(exec) => exec.internal == Internal::start_end_panel,
_ => false,
})
.filter_map(|v| v.keys.first())
.map(|&k| KEY_FORMAT.to_string(k))
.next()
.unwrap_or_else(|| ":start_end_panel".to_string());
let md = format!("hit *{shortcut}* to fill arg ");
screen.goto(w, area.left, area.top)?;
panel_skin.purpose_skin.write_composite_fill(
w,
Composite::from_inline(&md),
area.width as usize,
Alignment::Right,
)?;
}
Ok(())
}
}