reedline/prompt/
default.rs

1use crate::{Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode};
2
3use {
4    chrono::Local,
5    std::{borrow::Cow, env},
6};
7
8/// The default prompt indicator
9pub static DEFAULT_PROMPT_INDICATOR: &str = "〉";
10pub static DEFAULT_VI_INSERT_PROMPT_INDICATOR: &str = ": ";
11pub static DEFAULT_VI_NORMAL_PROMPT_INDICATOR: &str = "〉";
12pub static DEFAULT_MULTILINE_INDICATOR: &str = "::: ";
13
14/// Simple [`Prompt`] displaying a configurable left and a right prompt.
15/// For more fine-tuned configuration, implement the [`Prompt`] trait.
16/// For the default configuration, use [`DefaultPrompt::default()`]
17#[derive(Clone)]
18pub struct DefaultPrompt {
19    /// What segment should be rendered in the left (main) prompt
20    pub left_prompt: DefaultPromptSegment,
21    /// What segment should be rendered in the right prompt
22    pub right_prompt: DefaultPromptSegment,
23}
24
25/// A struct to control the appearance of the left or right prompt in a [`DefaultPrompt`]
26#[derive(Clone)]
27pub enum DefaultPromptSegment {
28    /// A basic user-defined prompt (i.e. just text)
29    Basic(String),
30    /// The path of the current working directory
31    WorkingDirectory,
32    /// The current date and time
33    CurrentDateTime,
34    /// An empty prompt segment
35    Empty,
36}
37
38/// Given a prompt segment, render it to a Cow<str> that we can use to
39/// easily implement [`Prompt`]'s `render_prompt_left` and `render_prompt_right`
40/// functions.
41fn render_prompt_segment(prompt: &DefaultPromptSegment) -> Cow<str> {
42    match &prompt {
43        DefaultPromptSegment::Basic(s) => Cow::Borrowed(s),
44        DefaultPromptSegment::WorkingDirectory => {
45            let prompt = get_working_dir().unwrap_or_else(|_| String::from("no path"));
46            Cow::Owned(prompt)
47        }
48        DefaultPromptSegment::CurrentDateTime => Cow::Owned(get_now()),
49        DefaultPromptSegment::Empty => Cow::Borrowed(""),
50    }
51}
52
53impl Prompt for DefaultPrompt {
54    fn render_prompt_left(&self) -> Cow<str> {
55        render_prompt_segment(&self.left_prompt)
56    }
57
58    fn render_prompt_right(&self) -> Cow<str> {
59        render_prompt_segment(&self.right_prompt)
60    }
61
62    fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
63        match edit_mode {
64            PromptEditMode::Default | PromptEditMode::Emacs => DEFAULT_PROMPT_INDICATOR.into(),
65            PromptEditMode::Vi(vi_mode) => match vi_mode {
66                PromptViMode::Normal => DEFAULT_VI_NORMAL_PROMPT_INDICATOR.into(),
67                PromptViMode::Insert => DEFAULT_VI_INSERT_PROMPT_INDICATOR.into(),
68            },
69            PromptEditMode::Custom(str) => format!("({str})").into(),
70        }
71    }
72
73    fn render_prompt_multiline_indicator(&self) -> Cow<str> {
74        Cow::Borrowed(DEFAULT_MULTILINE_INDICATOR)
75    }
76
77    fn render_prompt_history_search_indicator(
78        &self,
79        history_search: PromptHistorySearch,
80    ) -> Cow<str> {
81        let prefix = match history_search.status {
82            PromptHistorySearchStatus::Passing => "",
83            PromptHistorySearchStatus::Failing => "failing ",
84        };
85        // NOTE: magic strings, given there is logic on how these compose I am not sure if it
86        // is worth extracting in to static constant
87        Cow::Owned(format!(
88            "({}reverse-search: {}) ",
89            prefix, history_search.term
90        ))
91    }
92}
93
94impl Default for DefaultPrompt {
95    fn default() -> Self {
96        DefaultPrompt {
97            left_prompt: DefaultPromptSegment::WorkingDirectory,
98            right_prompt: DefaultPromptSegment::CurrentDateTime,
99        }
100    }
101}
102
103impl DefaultPrompt {
104    /// Constructor for the default prompt, which takes a configurable left and right prompt.
105    /// For less customization, use [`DefaultPrompt::default`].
106    /// For more fine-tuned configuration, implement the [`Prompt`] trait.
107    pub const fn new(
108        left_prompt: DefaultPromptSegment,
109        right_prompt: DefaultPromptSegment,
110    ) -> DefaultPrompt {
111        DefaultPrompt {
112            left_prompt,
113            right_prompt,
114        }
115    }
116}
117
118fn get_working_dir() -> Result<String, std::io::Error> {
119    let path = env::current_dir()?;
120    let path_str = path.display().to_string();
121    let homedir: String = match env::var("USERPROFILE") {
122        Ok(win_home) => win_home,
123        Err(_) => match env::var("HOME") {
124            Ok(maclin_home) => maclin_home,
125            Err(_) => path_str.clone(),
126        },
127    };
128    let new_path = if path_str != homedir {
129        path_str.replace(&homedir, "~")
130    } else {
131        path_str
132    };
133    Ok(new_path)
134}
135
136fn get_now() -> String {
137    let now = Local::now();
138    format!("{:>}", now.format("%m/%d/%Y %I:%M:%S %p"))
139}