diff --git a/src/repl/init.rs b/src/repl/init.rs index faafd9d..61351fc 100644 --- a/src/repl/init.rs +++ b/src/repl/init.rs @@ -5,17 +5,13 @@ use crate::config::{Config, SharedConfig}; use anyhow::{Context, Result}; use reedline::{ default_emacs_keybindings, ColumnarMenu, DefaultCompleter, Emacs, FileBackedHistory, KeyCode, - KeyModifiers, Keybindings, Prompt, PromptHistorySearch, PromptHistorySearchStatus, Reedline, - ReedlineEvent, ReedlineMenu, ValidationResult, Validator, + KeyModifiers, Keybindings, Reedline, ReedlineEvent, ReedlineMenu, ValidationResult, Validator, }; -use std::borrow::Cow; const MENU_NAME: &str = "completion_menu"; -const DEFAULT_MULTILINE_INDICATOR: &str = "::: "; pub struct Repl { pub editor: Reedline, - pub prompt: ReplPrompt, } impl Repl { @@ -25,7 +21,7 @@ impl Repl { .filter(|(_, _, v)| *v) .map(|(v, _, _)| *v) .collect(); - let completer = Self::create_completer(config.clone()); + let completer = Self::create_completer(config); let keybindings = Self::create_keybindings(); let history = Self::create_history()?; let menu = Self::create_menu(); @@ -39,8 +35,7 @@ impl Repl { .with_partial_completions(true) .with_validator(Box::new(ReplValidator { multiline_commands })) .with_ansi_colors(true); - let prompt = ReplPrompt(config); - Ok(Self { editor, prompt }) + Ok(Self { editor }) } fn create_completer(config: SharedConfig) -> DefaultCompleter { @@ -120,55 +115,3 @@ fn incomplete_brackets(line: &str, multiline_commands: &[&str]) -> bool { !balance.is_empty() } - -#[derive(Clone)] -pub struct ReplPrompt(SharedConfig); - -impl Prompt for ReplPrompt { - fn render_prompt_left(&self) -> Cow { - let config = self.0.lock(); - if let Some(role) = config.role.as_ref() { - role.name.to_string().into() - } else { - Cow::Borrowed("") - } - } - - fn render_prompt_right(&self) -> Cow { - let config = self.0.lock(); - if let Some(conversation) = config.conversation.as_ref() { - conversation.reamind_tokens().to_string().into() - } else { - Cow::Borrowed("") - } - } - - fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow { - let config = self.0.lock(); - if config.conversation.is_some() { - Cow::Borrowed("$") - } else { - Cow::Borrowed("〉") - } - } - - fn render_prompt_multiline_indicator(&self) -> Cow { - Cow::Borrowed(DEFAULT_MULTILINE_INDICATOR) - } - - fn render_prompt_history_search_indicator( - &self, - history_search: PromptHistorySearch, - ) -> Cow { - let prefix = match history_search.status { - PromptHistorySearchStatus::Passing => "", - PromptHistorySearchStatus::Failing => "failing ", - }; - // NOTE: magic strings, given there is logic on how these compose I am not sure if it - // is worth extracting in to static constant - Cow::Owned(format!( - "({}reverse-search: {}) ", - prefix, history_search.term - )) - } -} diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 661be75..9d7d1cf 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -1,10 +1,12 @@ mod abort; mod handler; mod init; +mod prompt; pub use self::abort::*; pub use self::handler::*; pub use self::init::Repl; +use self::prompt::ReplPrompt; use crate::client::ChatGptClient; use crate::config::SharedConfig; @@ -33,7 +35,8 @@ pub const REPL_COMMANDS: [(&str, &str, bool); 12] = [ impl Repl { pub fn run(&mut self, client: ChatGptClient, config: SharedConfig) -> Result<()> { let abort = AbortSignal::new(); - let handler = ReplCmdHandler::init(client, config, abort.clone())?; + let handler = ReplCmdHandler::init(client, config.clone(), abort.clone())?; + let prompt = ReplPrompt::new(config); print_now!("Welcome to aichat {}\n", env!("CARGO_PKG_VERSION")); print_now!("Type \".help\" for more information.\n"); let mut already_ctrlc = false; @@ -45,7 +48,7 @@ impl Repl { if abort.aborted_ctrlc() && !already_ctrlc { already_ctrlc = true; } - let sig = self.editor.read_line(&self.prompt); + let sig = self.editor.read_line(&prompt); match sig { Ok(Signal::Success(line)) => { already_ctrlc = false; diff --git a/src/repl/prompt.rs b/src/repl/prompt.rs new file mode 100644 index 0000000..4c56626 --- /dev/null +++ b/src/repl/prompt.rs @@ -0,0 +1,64 @@ +use crate::config::SharedConfig; + +use reedline::{Prompt, PromptHistorySearch, PromptHistorySearchStatus}; +use std::borrow::Cow; + +const DEFAULT_MULTILINE_INDICATOR: &str = "::: "; + +#[derive(Clone)] +pub struct ReplPrompt(SharedConfig); + +impl ReplPrompt { + pub fn new(config: SharedConfig) -> Self { + Self(config) + } +} + +impl Prompt for ReplPrompt { + fn render_prompt_left(&self) -> Cow { + let config = self.0.lock(); + if let Some(role) = config.role.as_ref() { + role.name.to_string().into() + } else { + Cow::Borrowed("") + } + } + + fn render_prompt_right(&self) -> Cow { + let config = self.0.lock(); + if let Some(conversation) = config.conversation.as_ref() { + conversation.reamind_tokens().to_string().into() + } else { + Cow::Borrowed("") + } + } + + fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow { + let config = self.0.lock(); + if config.conversation.is_some() { + Cow::Borrowed("$") + } else { + Cow::Borrowed("〉") + } + } + + fn render_prompt_multiline_indicator(&self) -> Cow { + Cow::Borrowed(DEFAULT_MULTILINE_INDICATOR) + } + + fn render_prompt_history_search_indicator( + &self, + history_search: PromptHistorySearch, + ) -> Cow { + let prefix = match history_search.status { + PromptHistorySearchStatus::Passing => "", + PromptHistorySearchStatus::Failing => "failing ", + }; + // NOTE: magic strings, given there is logic on how these compose I am not sure if it + // is worth extracting in to static constant + Cow::Owned(format!( + "({}reverse-search: {}) ", + prefix, history_search.term + )) + } +}