|
1 | 1 | use std::borrow::Cow;
|
2 | 2 |
|
3 |
| -use crossterm::style::Stylize; |
4 | 3 | use eyre::Result;
|
5 | 4 | use rustyline::completion::{
|
6 | 5 | Completer,
|
@@ -35,6 +34,8 @@ use rustyline::{
|
35 | 34 | };
|
36 | 35 | use winnow::stream::AsChar;
|
37 | 36 |
|
| 37 | +pub use super::prompt_parser::generate_prompt; |
| 38 | +use super::prompt_parser::parse_prompt_components; |
38 | 39 | use crate::database::Database;
|
39 | 40 | use crate::database::settings::Setting;
|
40 | 41 |
|
@@ -81,16 +82,6 @@ pub const COMMANDS: &[&str] = &[
|
81 | 82 | "/load",
|
82 | 83 | ];
|
83 | 84 |
|
84 |
| -pub fn generate_prompt(current_profile: Option<&str>, warning: bool) -> String { |
85 |
| - let warning_symbol = if warning { "!".red().to_string() } else { "".to_string() }; |
86 |
| - let profile_part = current_profile |
87 |
| - .filter(|&p| p != "default") |
88 |
| - .map(|p| format!("[{p}] ").cyan().to_string()) |
89 |
| - .unwrap_or_default(); |
90 |
| - |
91 |
| - format!("{profile_part}{warning_symbol}{}", "> ".magenta()) |
92 |
| -} |
93 |
| - |
94 | 85 | /// Complete commands that start with a slash
|
95 | 86 | fn complete_command(word: &str, start: usize) -> (usize, Vec<String>) {
|
96 | 87 | (
|
@@ -265,6 +256,34 @@ impl Highlighter for ChatHelper {
|
265 | 256 | fn highlight_char(&self, _line: &str, _pos: usize, _kind: CmdKind) -> bool {
|
266 | 257 | false
|
267 | 258 | }
|
| 259 | + |
| 260 | + fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, _default: bool) -> Cow<'b, str> { |
| 261 | + use crossterm::style::Stylize; |
| 262 | + |
| 263 | + // Parse the plain text prompt to extract profile and warning information |
| 264 | + // and apply colors using crossterm's ANSI escape codes |
| 265 | + if let Some(components) = parse_prompt_components(prompt) { |
| 266 | + let mut result = String::new(); |
| 267 | + |
| 268 | + // Add profile part if present |
| 269 | + if let Some(profile) = components.profile { |
| 270 | + result.push_str(&format!("[{}] ", profile).cyan().to_string()); |
| 271 | + } |
| 272 | + |
| 273 | + // Add warning symbol if present |
| 274 | + if components.warning { |
| 275 | + result.push_str(&"!".red().to_string()); |
| 276 | + } |
| 277 | + |
| 278 | + // Add the prompt symbol |
| 279 | + result.push_str(&"> ".magenta().to_string()); |
| 280 | + |
| 281 | + Cow::Owned(result) |
| 282 | + } else { |
| 283 | + // If we can't parse the prompt, return it as-is |
| 284 | + Cow::Borrowed(prompt) |
| 285 | + } |
| 286 | + } |
268 | 287 | }
|
269 | 288 |
|
270 | 289 | pub fn rl(
|
@@ -306,28 +325,10 @@ pub fn rl(
|
306 | 325 |
|
307 | 326 | #[cfg(test)]
|
308 | 327 | mod tests {
|
309 |
| - use super::*; |
310 |
| - |
311 |
| - #[test] |
312 |
| - fn test_generate_prompt() { |
313 |
| - // Test default prompt (no profile) |
314 |
| - assert_eq!(generate_prompt(None, false), "> ".magenta().to_string()); |
315 |
| - // Test default prompt with warning |
316 |
| - assert_eq!(generate_prompt(None, true), format!("{}{}", "!".red(), "> ".magenta())); |
317 |
| - // Test default profile (should be same as no profile) |
318 |
| - assert_eq!(generate_prompt(Some("default"), false), "> ".magenta().to_string()); |
319 |
| - // Test custom profile |
320 |
| - assert_eq!( |
321 |
| - generate_prompt(Some("test-profile"), false), |
322 |
| - format!("{}{}", "[test-profile] ".cyan(), "> ".magenta()) |
323 |
| - ); |
324 |
| - // Test another custom profile with warning |
325 |
| - assert_eq!( |
326 |
| - generate_prompt(Some("dev"), true), |
327 |
| - format!("{}{}{}", "[dev] ".cyan(), "!".red(), "> ".magenta()) |
328 |
| - ); |
329 |
| - } |
| 328 | + use crossterm::style::Stylize; |
| 329 | + use rustyline::highlight::Highlighter; |
330 | 330 |
|
| 331 | + use super::*; |
331 | 332 | #[test]
|
332 | 333 | fn test_chat_completer_command_completion() {
|
333 | 334 | let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>();
|
@@ -368,4 +369,87 @@ mod tests {
|
368 | 369 | // Verify no completions are returned for regular text
|
369 | 370 | assert!(completions.is_empty());
|
370 | 371 | }
|
| 372 | + |
| 373 | + #[test] |
| 374 | + fn test_highlight_prompt_basic() { |
| 375 | + let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>(); |
| 376 | + let (_, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>(); |
| 377 | + let helper = ChatHelper { |
| 378 | + completer: ChatCompleter::new(prompt_request_sender, prompt_response_receiver), |
| 379 | + hinter: (), |
| 380 | + validator: MultiLineValidator, |
| 381 | + }; |
| 382 | + |
| 383 | + // Test basic prompt highlighting |
| 384 | + let highlighted = helper.highlight_prompt("> ", true); |
| 385 | + |
| 386 | + assert_eq!(highlighted, "> ".magenta().to_string()); |
| 387 | + } |
| 388 | + |
| 389 | + #[test] |
| 390 | + fn test_highlight_prompt_with_warning() { |
| 391 | + let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>(); |
| 392 | + let (_, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>(); |
| 393 | + let helper = ChatHelper { |
| 394 | + completer: ChatCompleter::new(prompt_request_sender, prompt_response_receiver), |
| 395 | + hinter: (), |
| 396 | + validator: MultiLineValidator, |
| 397 | + }; |
| 398 | + |
| 399 | + // Test warning prompt highlighting |
| 400 | + let highlighted = helper.highlight_prompt("!> ", true); |
| 401 | + |
| 402 | + assert_eq!(highlighted, format!("{}{}", "!".red(), "> ".magenta())); |
| 403 | + } |
| 404 | + |
| 405 | + #[test] |
| 406 | + fn test_highlight_prompt_with_profile() { |
| 407 | + let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>(); |
| 408 | + let (_, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>(); |
| 409 | + let helper = ChatHelper { |
| 410 | + completer: ChatCompleter::new(prompt_request_sender, prompt_response_receiver), |
| 411 | + hinter: (), |
| 412 | + validator: MultiLineValidator, |
| 413 | + }; |
| 414 | + |
| 415 | + // Test profile prompt highlighting |
| 416 | + let highlighted = helper.highlight_prompt("[test-profile] > ", true); |
| 417 | + |
| 418 | + assert_eq!(highlighted, format!("{}{}", "[test-profile] ".cyan(), "> ".magenta())); |
| 419 | + } |
| 420 | + |
| 421 | + #[test] |
| 422 | + fn test_highlight_prompt_with_profile_and_warning() { |
| 423 | + let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>(); |
| 424 | + let (_, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>(); |
| 425 | + let helper = ChatHelper { |
| 426 | + completer: ChatCompleter::new(prompt_request_sender, prompt_response_receiver), |
| 427 | + hinter: (), |
| 428 | + validator: MultiLineValidator, |
| 429 | + }; |
| 430 | + |
| 431 | + // Test profile + warning prompt highlighting |
| 432 | + let highlighted = helper.highlight_prompt("[dev] !> ", true); |
| 433 | + // Should have cyan profile + red warning + cyan bold prompt |
| 434 | + assert_eq!( |
| 435 | + highlighted, |
| 436 | + format!("{}{}{}", "[dev] ".cyan(), "!".red(), "> ".magenta()) |
| 437 | + ); |
| 438 | + } |
| 439 | + |
| 440 | + #[test] |
| 441 | + fn test_highlight_prompt_invalid_format() { |
| 442 | + let (prompt_request_sender, _) = std::sync::mpsc::channel::<Option<String>>(); |
| 443 | + let (_, prompt_response_receiver) = std::sync::mpsc::channel::<Vec<String>>(); |
| 444 | + let helper = ChatHelper { |
| 445 | + completer: ChatCompleter::new(prompt_request_sender, prompt_response_receiver), |
| 446 | + hinter: (), |
| 447 | + validator: MultiLineValidator, |
| 448 | + }; |
| 449 | + |
| 450 | + // Test invalid prompt format (should return as-is) |
| 451 | + let invalid_prompt = "invalid prompt format"; |
| 452 | + let highlighted = helper.highlight_prompt(invalid_prompt, true); |
| 453 | + assert_eq!(highlighted, invalid_prompt); |
| 454 | + } |
371 | 455 | }
|
0 commit comments