use super::display_width::display_width;
use crate::env::ShellEnv;
use crate::expand::expand_word_to_string;
use crate::lexer::Lexer;
use crate::lexer::token::Token;
use crate::parser::ast::Word;
fn default_prompt(var_name: &str) -> &'static str {
match var_name {
"PS1" => {
if unsafe { libc::getuid() } == 0 {
"# "
} else {
"$ "
}
}
"PS2" => "> ",
_ => "",
}
}
fn parse_prompt_word(raw: &str) -> Word {
let input = format!("\"{}\"", raw);
let mut lexer = Lexer::new(&input);
match lexer.next_token() {
Ok(tok) => {
if let Token::Word(word) = tok.token {
word
} else {
Word::literal(raw)
}
}
Err(_) => {
Word::literal(raw)
}
}
}
pub fn expand_prompt(env: &mut ShellEnv, var_name: &str) -> String {
let raw = match env.vars.get(var_name) {
Some(v) => v.to_string(),
None => return default_prompt(var_name).to_string(),
};
if raw.is_empty() {
return String::new();
}
let word = parse_prompt_word(&raw);
expand_word_to_string(env, &word).unwrap_or(raw)
}
pub struct PromptInfo {
pub upper_lines: Vec<String>,
pub last_line: String,
#[allow(dead_code)] pub last_line_width: usize,
}
impl PromptInfo {
pub fn from_prompt(prompt: &str) -> Self {
let lines: Vec<&str> = prompt.split('\n').collect();
if lines.len() <= 1 {
let last_line = prompt.to_string();
let last_line_width = display_width(&last_line);
PromptInfo {
upper_lines: Vec::new(),
last_line,
last_line_width,
}
} else {
let upper_lines: Vec<String> = lines[..lines.len() - 1]
.iter()
.map(|s| s.to_string())
.collect();
let last_line = lines[lines.len() - 1].to_string();
let last_line_width = display_width(&last_line);
PromptInfo {
upper_lines,
last_line,
last_line_width,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_line_prompt() {
let info = PromptInfo::from_prompt("$ ");
assert!(info.upper_lines.is_empty());
assert_eq!(info.last_line, "$ ");
assert_eq!(info.last_line_width, 2);
}
#[test]
fn multi_line_prompt() {
let info = PromptInfo::from_prompt("~/proj main\n❯ ");
assert_eq!(info.upper_lines, vec!["~/proj main"]);
assert_eq!(info.last_line, "❯ ");
assert_eq!(info.last_line_width, 2); }
#[test]
fn multi_line_with_ansi() {
let prompt = "\x1b[34m~/proj\x1b[0m \x1b[32m main\x1b[0m\n\x1b[1;35m❯\x1b[0m ";
let info = PromptInfo::from_prompt(prompt);
assert_eq!(info.upper_lines.len(), 1);
assert_eq!(
info.upper_lines[0],
"\x1b[34m~/proj\x1b[0m \x1b[32m main\x1b[0m"
);
assert_eq!(info.last_line, "\x1b[1;35m❯\x1b[0m ");
assert_eq!(info.last_line_width, 2);
}
#[test]
fn three_line_prompt() {
let info = PromptInfo::from_prompt("line1\nline2\n$ ");
assert_eq!(info.upper_lines, vec!["line1", "line2"]);
assert_eq!(info.last_line, "$ ");
assert_eq!(info.last_line_width, 2);
}
#[test]
fn empty_prompt() {
let info = PromptInfo::from_prompt("");
assert!(info.upper_lines.is_empty());
assert_eq!(info.last_line, "");
assert_eq!(info.last_line_width, 0);
}
}