use crate::{Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode};
use {
chrono::Local,
std::{borrow::Cow, env},
};
pub static DEFAULT_PROMPT_INDICATOR: &str = "> ";
pub static DEFAULT_VI_INSERT_PROMPT_INDICATOR: &str = ": ";
pub static DEFAULT_VI_NORMAL_PROMPT_INDICATOR: &str = "> ";
pub static DEFAULT_MULTILINE_INDICATOR: &str = "::: ";
#[derive(Clone)]
pub struct DefaultPrompt {
pub left_prompt: DefaultPromptSegment,
pub right_prompt: DefaultPromptSegment,
}
#[derive(Clone)]
pub enum DefaultPromptSegment {
Basic(String),
WorkingDirectory,
CurrentDateTime,
Empty,
}
fn render_prompt_segment(prompt: &DefaultPromptSegment) -> Cow<'_, str> {
match &prompt {
DefaultPromptSegment::Basic(s) => Cow::Borrowed(s),
DefaultPromptSegment::WorkingDirectory => {
let prompt = get_working_dir().unwrap_or_else(|_| String::from("no path"));
Cow::Owned(prompt)
}
DefaultPromptSegment::CurrentDateTime => Cow::Owned(get_now()),
DefaultPromptSegment::Empty => Cow::Borrowed(""),
}
}
impl Prompt for DefaultPrompt {
fn render_prompt_left(&self) -> Cow<'_, str> {
render_prompt_segment(&self.left_prompt)
}
fn render_prompt_right(&self) -> Cow<'_, str> {
render_prompt_segment(&self.right_prompt)
}
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<'_, str> {
match edit_mode {
PromptEditMode::Default | PromptEditMode::Emacs => DEFAULT_PROMPT_INDICATOR.into(),
PromptEditMode::Vi(vi_mode) => match vi_mode {
PromptViMode::Normal | PromptViMode::Visual => {
DEFAULT_VI_NORMAL_PROMPT_INDICATOR.into()
}
PromptViMode::Insert => DEFAULT_VI_INSERT_PROMPT_INDICATOR.into(),
},
PromptEditMode::Custom(str) => format!("({str})").into(),
}
}
fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> {
Cow::Borrowed(DEFAULT_MULTILINE_INDICATOR)
}
fn render_prompt_history_search_indicator(
&self,
history_search: PromptHistorySearch,
) -> Cow<'_, str> {
let prefix = match history_search.status {
PromptHistorySearchStatus::Passing => "",
PromptHistorySearchStatus::Failing => "failing ",
};
Cow::Owned(format!(
"({}reverse-search: {}) ",
prefix, history_search.term
))
}
}
impl Default for DefaultPrompt {
fn default() -> Self {
DefaultPrompt {
left_prompt: DefaultPromptSegment::WorkingDirectory,
right_prompt: DefaultPromptSegment::CurrentDateTime,
}
}
}
impl DefaultPrompt {
pub const fn new(
left_prompt: DefaultPromptSegment,
right_prompt: DefaultPromptSegment,
) -> DefaultPrompt {
DefaultPrompt {
left_prompt,
right_prompt,
}
}
}
fn get_working_dir() -> Result<String, std::io::Error> {
let cwd = env::current_dir()?;
let home = env::var_os("USERPROFILE")
.or_else(|| env::var_os("HOME"))
.map(std::path::PathBuf::from);
Ok(format_working_dir(&cwd, home.as_deref()))
}
fn format_working_dir(cwd: &std::path::Path, home: Option<&std::path::Path>) -> String {
if let Some(home) = home {
if let Ok(suffix) = cwd.strip_prefix(home) {
let mut path = std::path::PathBuf::from("~");
if !suffix.as_os_str().is_empty() {
path = path.join(suffix);
}
return path.display().to_string();
}
}
cwd.display().to_string()
}
fn get_now() -> String {
let now = Local::now();
format!("{:>}", now.format("%m/%d/%Y %I:%M:%S %p"))
}
#[cfg(test)]
mod tests {
use super::format_working_dir;
use std::path::{Path, PathBuf};
#[cfg(unix)]
#[test]
fn home_is_collapsed_to_tilde() {
let home = Path::new("/home/alice");
let cwd = PathBuf::from("/home/alice/projects");
assert_eq!(format_working_dir(&cwd, Some(home)), "~/projects");
}
#[cfg(unix)]
#[test]
fn cwd_equal_to_home_is_just_tilde() {
let home = Path::new("/home/alice");
let cwd = PathBuf::from("/home/alice");
assert_eq!(format_working_dir(&cwd, Some(home)), "~");
}
#[cfg(unix)]
#[test]
fn shared_prefix_is_not_collapsed() {
let home = Path::new("/home/alice");
let cwd = PathBuf::from("/home/alicebob/x");
assert_eq!(format_working_dir(&cwd, Some(home)), "/home/alicebob/x");
}
#[cfg(unix)]
#[test]
fn missing_home_leaves_path_untouched() {
let cwd = PathBuf::from("/var/log");
assert_eq!(format_working_dir(&cwd, None), "/var/log");
}
#[cfg(windows)]
#[test]
fn home_is_collapsed_to_tilde() {
let home = Path::new(r"C:\Users\alice");
let cwd = PathBuf::from(r"C:\Users\alice\projects");
assert_eq!(format_working_dir(&cwd, Some(home)), r"~\projects");
}
#[cfg(windows)]
#[test]
fn cwd_equal_to_home_is_just_tilde() {
let home = Path::new(r"C:\Users\alice");
let cwd = PathBuf::from(r"C:\Users\alice");
assert_eq!(format_working_dir(&cwd, Some(home)), "~");
}
#[cfg(windows)]
#[test]
fn shared_prefix_is_not_collapsed() {
let home = Path::new(r"C:\Users\alice");
let cwd = PathBuf::from(r"C:\Users\alicebob\x");
assert_eq!(format_working_dir(&cwd, Some(home)), r"C:\Users\alicebob\x");
}
#[cfg(windows)]
#[test]
fn missing_home_leaves_path_untouched() {
let cwd = PathBuf::from(r"C:\Windows\System32");
assert_eq!(format_working_dir(&cwd, None), r"C:\Windows\System32");
}
}