use eframe::egui::{self, RichText};
use crate::app::components::{terminal, widgets};
use crate::app::page::{Intent, PageCtx};
use crate::docker::{ContainerInfo, TARGET_ROOT, TermOp, debug_image, next_session};
#[derive(Default)]
pub(super) struct TerminalTab {
shell: String,
size: (u16, u16),
shown_session: u64,
}
pub(super) fn show(ui: &mut egui::Ui, ctx: &mut PageCtx, tab: &mut TerminalTab, c: &ContainerInfo) {
let p = ctx.palette;
if !c.is_running() {
ui.add_space(20.0);
ui.label(RichText::new("Start the container to open a shell in it.").color(p.muted));
return;
}
let term = ctx
.store
.terminal
.as_ref()
.filter(|t| t.container_id == c.id);
let mut open = term.is_none().then_some(false);
ui.horizontal(|ui| {
let label = ui.label(RichText::new("Shell").color(p.muted));
ui.add(
egui::TextEdit::singleline(&mut tab.shell)
.hint_text("bash or sh")
.desired_width(110.0),
)
.labelled_by(label.id);
if ui
.button("New session")
.on_hover_text("Close this shell and start another")
.clicked()
{
open = Some(term.is_some_and(|t| t.debug));
}
if let Some(t) = term {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if widgets::icon_button(
ui,
crate::app::components::icons::Icon::Close,
"End the session",
)
.clicked()
{
ctx.request(Intent::Terminal(TermOp::Close { session: t.session }));
}
});
}
});
ui.add_space(8.0);
if let Some(debug) = open {
let (cols, rows) = if tab.size.0 > 0 { tab.size } else { (100, 30) };
ctx.request(Intent::Terminal(TermOp::Open {
session: next_session(),
container: c.id.clone(),
shell: tab.shell.trim().to_owned(),
debug,
cols,
rows,
}));
tab.size = (cols, rows);
return;
}
let Some(term) = term else { return };
if let Some(ended) = &term.ended {
let (text, color) = match ended {
Some(why) => (why.as_str(), p.danger),
None => (
"The shell exited. Start a new session to open another.",
p.muted,
),
};
ui.label(RichText::new(text).color(color));
if term.no_shell && !term.debug {
ui.add_space(6.0);
ui.horizontal(|ui| {
if widgets::primary_button(ui, "Open a debug shell").clicked() {
let (cols, rows) = tab.size;
ctx.request(Intent::Terminal(TermOp::Open {
session: next_session(),
container: c.id.clone(),
shell: String::new(),
debug: true,
cols: cols.max(20),
rows: rows.max(4),
}));
}
ui.label(
RichText::new(format!(
"Runs {} next to the container, sharing its processes and network.",
debug_image()
))
.size(12.0)
.color(p.muted),
);
});
}
ui.add_space(6.0);
} else if term.debug {
ui.label(
RichText::new(format!(
"Debug shell: {} next to {}, sharing its processes and network. \
Its files are in {TARGET_ROOT}.",
debug_image(),
c.name
))
.size(12.0)
.color(p.muted),
);
ui.add_space(6.0);
}
let focus = tab.shown_session != term.session;
tab.shown_session = term.session;
let out = terminal::show(ui, p, term.parser.screen(), focus);
if term.ended.is_none() {
if !out.input.is_empty() {
ctx.request(Intent::Terminal(TermOp::Input {
session: term.session,
bytes: out.input,
}));
}
if out.size != term.parser.screen().size().swap() {
tab.size = out.size;
ctx.request(Intent::Terminal(TermOp::Resize {
session: term.session,
cols: out.size.0,
rows: out.size.1,
}));
}
}
}
trait Swap {
fn swap(self) -> Self;
}
impl Swap for (u16, u16) {
fn swap(self) -> Self {
(self.1, self.0)
}
}