use std::collections::BTreeSet;
use std::sync::Arc;
use crate::completion::CompletionTree;
use super::super::history_store::HistoryConfig;
pub(crate) const DEFAULT_HISTORY_MENU_ROWS: u16 = 5;
#[derive(Debug, Clone)]
pub struct ReplPrompt {
pub left: String,
pub indicator: String,
}
pub type PromptRightRenderer = Arc<dyn Fn() -> String + Send + Sync>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[must_use]
pub struct LineProjection {
pub line: String,
pub hidden_suggestions: BTreeSet<String>,
}
impl LineProjection {
pub fn passthrough(line: impl Into<String>) -> Self {
Self {
line: line.into(),
hidden_suggestions: BTreeSet::new(),
}
}
pub fn with_hidden_suggestions(mut self, hidden_suggestions: BTreeSet<String>) -> Self {
self.hidden_suggestions = hidden_suggestions;
self
}
}
pub type LineProjector = Arc<dyn Fn(&str) -> LineProjection + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReplInputMode {
Auto,
Interactive,
Basic,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplReloadKind {
Default,
WithIntro,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplLineResult {
Continue(String),
ReplaceInput(String),
Exit(i32),
Restart {
output: String,
reload: ReplReloadKind,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReplRunResult {
Exit(i32),
Restart {
output: String,
reload: ReplReloadKind,
},
}
#[non_exhaustive]
#[must_use]
pub struct ReplRunConfig {
pub prompt: ReplPrompt,
pub completion_words: Vec<String>,
pub completion_tree: Option<CompletionTree>,
pub appearance: ReplAppearance,
pub history_config: HistoryConfig,
pub input_mode: ReplInputMode,
pub prompt_right: Option<PromptRightRenderer>,
pub line_projector: Option<LineProjector>,
}
impl ReplRunConfig {
pub fn new(prompt: ReplPrompt, history_config: HistoryConfig) -> Self {
Self {
prompt,
completion_words: Vec::new(),
completion_tree: None,
appearance: ReplAppearance::default(),
history_config,
input_mode: ReplInputMode::Auto,
prompt_right: None,
line_projector: None,
}
}
pub fn builder(prompt: ReplPrompt, history_config: HistoryConfig) -> ReplRunConfigBuilder {
ReplRunConfigBuilder::new(prompt, history_config)
}
}
#[must_use]
pub struct ReplRunConfigBuilder {
config: ReplRunConfig,
}
impl ReplRunConfigBuilder {
pub fn new(prompt: ReplPrompt, history_config: HistoryConfig) -> Self {
Self {
config: ReplRunConfig::new(prompt, history_config),
}
}
pub fn with_completion_words<I, S>(mut self, completion_words: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.config.completion_words = completion_words.into_iter().map(Into::into).collect();
self
}
pub fn with_completion_tree(mut self, completion_tree: Option<CompletionTree>) -> Self {
self.config.completion_tree = completion_tree;
self
}
pub fn with_appearance(mut self, appearance: ReplAppearance) -> Self {
self.config.appearance = appearance;
self
}
pub fn with_history_config(mut self, history_config: HistoryConfig) -> Self {
self.config.history_config = history_config;
self
}
pub fn with_input_mode(mut self, input_mode: ReplInputMode) -> Self {
self.config.input_mode = input_mode;
self
}
pub fn with_prompt_right(mut self, prompt_right: Option<PromptRightRenderer>) -> Self {
self.config.prompt_right = prompt_right;
self
}
pub fn with_line_projector(mut self, line_projector: Option<LineProjector>) -> Self {
self.config.line_projector = line_projector;
self
}
pub fn build(self) -> ReplRunConfig {
self.config
}
}
impl ReplPrompt {
pub fn simple(left: impl Into<String>) -> Self {
Self {
left: left.into(),
indicator: String::new(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
#[must_use]
pub struct ReplAppearance {
pub completion_text_style: Option<String>,
pub completion_background_style: Option<String>,
pub completion_highlight_style: Option<String>,
pub command_highlight_style: Option<String>,
pub history_menu_rows: u16,
}
impl ReplAppearance {
pub fn builder() -> ReplAppearanceBuilder {
ReplAppearanceBuilder::new()
}
}
impl Default for ReplAppearance {
fn default() -> Self {
Self {
completion_text_style: None,
completion_background_style: None,
completion_highlight_style: None,
command_highlight_style: None,
history_menu_rows: DEFAULT_HISTORY_MENU_ROWS,
}
}
}
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct ReplAppearanceBuilder {
appearance: ReplAppearance,
}
impl ReplAppearanceBuilder {
pub fn new() -> Self {
Self {
appearance: ReplAppearance::default(),
}
}
pub fn with_completion_text_style(mut self, completion_text_style: Option<String>) -> Self {
self.appearance.completion_text_style = completion_text_style;
self
}
pub fn with_completion_background_style(
mut self,
completion_background_style: Option<String>,
) -> Self {
self.appearance.completion_background_style = completion_background_style;
self
}
pub fn with_completion_highlight_style(
mut self,
completion_highlight_style: Option<String>,
) -> Self {
self.appearance.completion_highlight_style = completion_highlight_style;
self
}
pub fn with_command_highlight_style(mut self, command_highlight_style: Option<String>) -> Self {
self.appearance.command_highlight_style = command_highlight_style;
self
}
pub fn with_history_menu_rows(mut self, history_menu_rows: u16) -> Self {
self.appearance.history_menu_rows = history_menu_rows;
self
}
pub fn build(self) -> ReplAppearance {
self.appearance
}
}
#[cfg(test)]
mod tests {
use super::{
ReplAppearance, ReplInputMode, ReplPrompt, ReplReloadKind, ReplRunConfig, ReplRunResult,
};
use crate::repl::HistoryConfig;
#[test]
fn run_config_builder_captures_host_surface_choices() {
let appearance = ReplAppearance::builder()
.with_history_menu_rows(8)
.with_command_highlight_style(Some("green".to_string()))
.build();
let config = ReplRunConfig::builder(
ReplPrompt::simple("osp> "),
HistoryConfig::builder().build(),
)
.with_completion_words(["help", "exit"])
.with_appearance(appearance.clone())
.with_input_mode(ReplInputMode::Basic)
.build();
assert_eq!(config.prompt.left, "osp> ");
assert_eq!(config.input_mode, ReplInputMode::Basic);
assert_eq!(
config.completion_words,
vec!["help".to_string(), "exit".to_string()]
);
assert_eq!(config.appearance.history_menu_rows, 8);
assert_eq!(
config.appearance.command_highlight_style.as_deref(),
Some("green")
);
}
#[test]
fn prompt_and_restart_outcomes_stay_plain_semantic_payloads() {
let prompt = ReplPrompt::simple("osp");
assert_eq!(prompt.left, "osp");
assert!(prompt.indicator.is_empty());
let restart = ReplRunResult::Restart {
output: "reloading".to_string(),
reload: ReplReloadKind::WithIntro,
};
assert!(matches!(
restart,
ReplRunResult::Restart {
output,
reload: ReplReloadKind::WithIntro
} if output == "reloading"
));
}
}