use crate::AppModel;
use crate::boundary::{Action, Gesture, help, line, reply::Reply, reply::encode};
use crate::cli_outbound::Cli;
use super::ShellState;
const RUN_HINT: &str = "Run this slash command — the same gesture the buttons and keys fire, typed \
instead of clicked. Its answer, or the reason it was refused, appears below. \
Enter runs it without leaving the box.";
pub(super) fn run(
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
typed: &str,
) -> bool {
let ctx = line::Context {
prepared: state.start.pending.clone(),
..model.line_context()
};
match line::parse(typed, &ctx) {
Err(reason) => {
state.slash = Some(reason);
false
}
Ok(Gesture::Ask(query)) => {
let deps = model.boundary_deps(lernie, bl);
let answer = model.answer(&deps, &query, super::now_unix());
note(state, answer)
}
Ok(Gesture::Act(action)) => act(model, state, lernie, bl, &action),
}
}
fn act(
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
action: &Action,
) -> bool {
let ts = super::now_ts();
match action {
Action::Prepare { workspace, payload } => {
let prepared = model.prepare_start(lernie, bl, workspace, payload, &ts);
if let Ok(ready) = prepared.as_ref() {
state.start.pending = Some(ready.clone());
}
note(state, prepared.map(Reply::Prepared))
}
Action::Prompt { prepared, goal } => {
let seed = state.start.mint_seed;
let fired = model.fire_prompt(lernie, bl, prepared, goal, seed, &ts);
if fired.is_ok() {
state.start.spend_mint();
state.start.pending = None;
}
note(
state,
fired.map(|conversation| Reply::Started { conversation }),
)
}
_ => {
let deps = model.boundary_deps(lernie, bl);
let result = model.dispatch(&deps, &ts, action);
match action.project() {
Some(project) => model.after_bl_verb(&project),
None => model.after_lernie_verb(),
}
note(state, result)
}
}
}
fn note(state: &mut ShellState, result: Result<Reply, String>) -> bool {
match result {
Ok(Reply::Help(rows)) => {
state.slash = Some(help::render(&rows));
true
}
Ok(Reply::Search(_)) => {
state.slash = None;
super::focus::center(state, crate::keymap::CenterTab::Search);
true
}
Ok(reply) => {
state.slash = Some(
serde_json::to_string_pretty(&encode(&reply))
.unwrap_or_else(|e| format!("unreadable reply: {e}")),
);
true
}
Err(reason) => {
state.slash = Some(reason);
false
}
}
}
pub(super) fn seat(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
ctx: &super::verb_row::VerbCtx,
) -> bool {
if !line::is_command(&ctx.text) {
return false;
}
let clicked = ui
.horizontal(|ui| ui.button("Run").on_hover_text(RUN_HINT).clicked())
.inner;
if clicked || ctx.entered {
super::focus::request(state);
if run(model, state, lernie, bl, &ctx.text) {
state.actions.drafts.set(ctx.key.clone(), String::new());
}
}
true
}
pub(super) fn note_ui(ui: &mut egui::Ui, state: &ShellState) {
let Some(note) = state.slash.clone() else {
return;
};
egui::ScrollArea::both()
.id_salt("slash-note")
.max_height(160.0)
.show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label(egui::RichText::new(note).monospace());
});
}