Skip to main content

osp_cli/repl/
engine.rs

1//! The REPL engine exists to own the editor/runtime boundary of interactive
2//! `osp`.
3//!
4//! High-level flow:
5//!
6//! - configure the line editor or basic fallback based on terminal capability
7//! - render prompts and prompt-right state for the current REPL frame
8//! - adapt completion, history search, and highlighting into editor-facing
9//!   menus and callbacks
10//! - expose debug/trace surfaces so host commands can inspect the live editor
11//!   state without reimplementing it
12//!
13//! Higher-level orchestration lives in [`super::host`] and
14//! [`super::lifecycle`]. Actual command execution lives in [`super::dispatch`].
15//!
16//! Contract:
17//!
18//! - this module may depend on editor adapters, completion, history, and UI
19//!   presentation helpers
20//! - it should not become the owner of generic command execution, config
21//!   precedence, or product-level restart policy
22//!
23//! Public API shape:
24//!
25//! - debug snapshots stay direct semantic payloads
26//! - host-facing REPL prompts, appearance, and run configuration live in the
27//!   dedicated [`config`] surface instead of inside the editor mechanics
28//! - host-style REPL runtime configuration uses concrete builders and
29//!   constructors such as [`ReplRunConfig::builder`],
30//!   [`ReplAppearance::builder`], and [`CompletionDebugOptions::new`]
31
32pub use super::highlight::{HighlightDebugSpan, debug_highlight};
33pub(crate) use super::history_store::expand_history;
34pub use super::history_store::{
35    HistoryConfig, HistoryConfigBuilder, HistoryEntry, HistoryShellContext, SharedHistory,
36};
37use crate::completion::CompletionTree;
38use anyhow::Result;
39
40mod adapter;
41mod config;
42mod debug;
43mod editor;
44mod overlay;
45mod session;
46
47pub(crate) use adapter::{
48    CompletionTraceEvent, CompletionTraceMenuState, trace_completion, trace_completion_enabled,
49};
50#[cfg(test)]
51pub(crate) use adapter::{
52    ReplCompleter, ReplHistoryCompleter, build_repl_highlighter, expand_home, path_suggestions,
53    split_path_stub,
54};
55pub use adapter::{color_from_style_spec, default_pipe_verbs};
56pub use config::{
57    LineProjection, LineProjector, PromptRightRenderer, ReplAppearance, ReplAppearanceBuilder,
58    ReplInputMode, ReplLineResult, ReplPrompt, ReplReloadKind, ReplRunConfig, ReplRunConfigBuilder,
59    ReplRunResult,
60};
61pub use debug::{
62    CompletionDebug, CompletionDebugFrame, CompletionDebugMatch, CompletionDebugOptions, DebugStep,
63    debug_completion, debug_completion_steps, debug_history_menu, debug_history_menu_steps,
64};
65#[cfg(test)]
66use editor::{
67    AutoCompleteEmacs, contains_cursor_position_report, is_cursor_position_error,
68    parse_cursor_position_report,
69};
70pub(crate) use editor::{BasicInputReason, OspPrompt, basic_input_reason};
71#[cfg(test)]
72use overlay::{build_history_menu, build_history_picker_options, history_picker_items};
73use session::{InteractiveLoopConfig, SubmissionContext, run_repl_basic, run_repl_interactive};
74#[cfg(test)]
75use session::{SubmissionResult, process_submission};
76
77const COMPLETION_MENU_NAME: &str = "completion_menu";
78const HISTORY_MENU_NAME: &str = "history_menu";
79const HOST_COMMAND_HISTORY_PICKER: &str = "\u{0}osp-repl-history-picker";
80
81struct ReplRunContext {
82    prompt: OspPrompt,
83    completion_words: Vec<String>,
84    completion_tree: Option<CompletionTree>,
85    appearance: ReplAppearance,
86    line_projector: Option<LineProjector>,
87    history_store: SharedHistory,
88}
89
90/// Runs the interactive REPL and delegates submitted lines to `execute`.
91pub fn run_repl<F>(config: ReplRunConfig, mut execute: F) -> Result<ReplRunResult>
92where
93    F: FnMut(&str, &SharedHistory) -> Result<ReplLineResult>,
94{
95    let ReplRunConfig {
96        prompt,
97        completion_words,
98        completion_tree,
99        appearance,
100        history_config,
101        input_mode,
102        prompt_right,
103        line_projector,
104    } = config;
105    let history_store = SharedHistory::new(history_config)?;
106    let mut submission = SubmissionContext {
107        history_store: &history_store,
108        execute: &mut execute,
109    };
110    let prompt = OspPrompt::new(prompt.left, prompt.indicator, prompt_right);
111    let basic_reason = basic_input_reason(input_mode);
112
113    run_repl_with_reason(
114        ReplRunContext {
115            prompt,
116            completion_words,
117            completion_tree,
118            appearance,
119            line_projector,
120            history_store: history_store.clone(),
121        },
122        basic_reason,
123        &mut submission,
124        run_repl_basic,
125        run_repl_interactive,
126    )
127}
128
129fn run_repl_with_reason<F, B, I>(
130    context: ReplRunContext,
131    basic_reason: Option<BasicInputReason>,
132    submission: &mut SubmissionContext<'_, F>,
133    mut run_basic_fn: B,
134    mut run_interactive_fn: I,
135) -> Result<ReplRunResult>
136where
137    F: FnMut(&str, &SharedHistory) -> Result<ReplLineResult>,
138    B: FnMut(&OspPrompt, &mut SubmissionContext<'_, F>) -> Result<()>,
139    I: FnMut(
140        InteractiveLoopConfig<'_>,
141        SharedHistory,
142        &mut SubmissionContext<'_, F>,
143    ) -> Result<ReplRunResult>,
144{
145    let ReplRunContext {
146        prompt,
147        completion_words,
148        completion_tree,
149        appearance,
150        line_projector,
151        history_store,
152    } = context;
153
154    if let Some(reason) = basic_reason {
155        match reason {
156            BasicInputReason::NotATerminal => {
157                eprintln!("Warning: Input is not a terminal (fd=0).");
158            }
159            BasicInputReason::CursorProbeUnsupported => {
160                eprintln!(
161                    "Warning: terminal does not support cursor position requests; using basic input mode."
162                );
163            }
164            BasicInputReason::Explicit => {}
165        }
166        run_basic_fn(&prompt, submission)?;
167        return Ok(ReplRunResult::Exit(0));
168    }
169
170    run_interactive_fn(
171        InteractiveLoopConfig {
172            prompt: &prompt,
173            completion_words,
174            completion_tree,
175            appearance,
176            line_projector,
177        },
178        history_store,
179        submission,
180    )
181}
182
183#[cfg(test)]
184mod tests;