diff --git a/Cargo.lock b/Cargo.lock index 83281dda..dd4c220b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bitflags" version = "1.2.1" @@ -12,6 +18,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "crossterm" version = "0.19.0" @@ -108,6 +127,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -146,6 +184,7 @@ dependencies = [ name = "reedline" version = "0.1.0" dependencies = [ + "chrono", "crossterm", "unicode-segmentation", ] @@ -193,12 +232,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "unicode-segmentation" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index c1919439..40fe65a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] crossterm = "0.19.0" unicode-segmentation = "1.7.1" +chrono = "0.4.19" diff --git a/src/engine.rs b/src/engine.rs index 8b7bf6c3..465b9ba4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,18 +1,16 @@ -use std::io::{Stdout, Write}; -use std::ops::Deref; - +use crate::history_search::{BasicSearch, BasicSearchCommand}; +use crate::line_buffer::LineBuffer; +use crate::Prompt; use crossterm::{ - cursor::{MoveToColumn, RestorePosition, SavePosition}, + cursor::{position, MoveToColumn, RestorePosition, SavePosition}, event::{read, Event, KeyCode, KeyEvent, KeyModifiers}, style::{Color, Print, ResetColor, SetForegroundColor}, terminal::{Clear, ClearType}, QueueableCommand, Result, }; - use std::collections::VecDeque; - -use crate::history_search::{BasicSearch, BasicSearchCommand}; -use crate::line_buffer::LineBuffer; +use std::io::{Stdout, Write}; +use std::ops::Deref; const HISTORY_SIZE: usize = 100; @@ -82,20 +80,23 @@ pub fn print_crlf(stdout: &mut Stdout) -> Result<()> { } fn queue_prompt(stdout: &mut Stdout) -> Result<()> { + let mut prompt = Prompt::new(); + prompt.set_prompt_indicator("〉".to_string()); + // print our prompt stdout .queue(MoveToColumn(0))? .queue(SetForegroundColor(Color::Blue))? - .queue(Print("〉"))? + .queue(Print(prompt.print_prompt()))? .queue(ResetColor)?; Ok(()) } -fn buffer_paint(stdout: &mut Stdout, engine: &Engine) -> Result<()> { +fn buffer_paint(stdout: &mut Stdout, engine: &Engine, prompt_offset: u16) -> Result<()> { let new_index = engine.get_insertion_point(); - queue_prompt(stdout)?; + // queue_prompt(stdout)?; // Repaint logic: // @@ -105,7 +106,9 @@ fn buffer_paint(stdout: &mut Stdout, engine: &Engine) -> Result<()> { // Then draw the remainer of the buffer from above // Finally, reset the cursor to the saved position - stdout.queue(Print(&engine.line_buffer[..new_index]))?; + // stdout.queue(Print(&engine.line_buffer[..new_index]))?; + stdout.queue(MoveToColumn(prompt_offset))?; + stdout.queue(Print(&engine.line_buffer[0..new_index]))?; stdout.queue(SavePosition)?; stdout.queue(Print(&engine.line_buffer[new_index..]))?; stdout.queue(Clear(ClearType::UntilNewLine))?; @@ -118,7 +121,10 @@ fn buffer_paint(stdout: &mut Stdout, engine: &Engine) -> Result<()> { fn history_search_paint(stdout: &mut Stdout, engine: &Engine) -> Result<()> { // Assuming we are currently searching - let search = engine.history_search.as_ref().unwrap(); + let search = engine + .history_search + .as_ref() + .expect("couldn't get history_search reference"); let status = if search.result.is_none() && !search.search_string.is_empty() { "failed " @@ -180,15 +186,24 @@ impl Engine { for command in commands { match command { EditCommand::InsertChar(c) => { - let search = self.history_search.as_mut().unwrap(); // We checked it is some + let search = self + .history_search + .as_mut() + .expect("couldn't get history_search as mutable"); // We checked it is some search.step(BasicSearchCommand::InsertChar(*c), &self.history); } EditCommand::Backspace => { - let search = self.history_search.as_mut().unwrap(); // We checked it is some + let search = self + .history_search + .as_mut() + .expect("couldn't get history_search as mutable"); // We checked it is some search.step(BasicSearchCommand::Backspace, &self.history); } EditCommand::SearchHistory => { - let search = self.history_search.as_mut().unwrap(); // We checked it is some + let search = self + .history_search + .as_mut() + .expect("couldn't get history_search as mutable"); // We checked it is some search.step(BasicSearchCommand::Next, &self.history); } EditCommand::MoveRight => { @@ -439,6 +454,10 @@ impl Engine { queue_prompt(stdout)?; stdout.flush()?; + // set where the input begins + let (mut prompt_offset, _) = position()?; + prompt_offset += 1; + loop { match read()? { Event::Key(KeyEvent { @@ -603,7 +622,7 @@ impl Engine { if self.history_search.is_some() { history_search_paint(stdout, &self)?; } else { - buffer_paint(stdout, &self)?; + buffer_paint(stdout, &self, prompt_offset)?; } } } diff --git a/src/main.rs b/src/main.rs index 73d87bff..0ade57ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,9 @@ use diagnostic::print_events; mod history_search; +mod prompt; +use prompt::Prompt; + fn main() -> Result<()> { let mut stdout = stdout(); diff --git a/src/prompt.rs b/src/prompt.rs new file mode 100644 index 00000000..adddcfe3 --- /dev/null +++ b/src/prompt.rs @@ -0,0 +1,110 @@ +use chrono::Local; +use crossterm::terminal; +use std::env; + +pub struct Prompt { + // The prompt symbol like > + prompt_indicator: String, + // The string for the left side of the prompt + left_prompt: String, + // The length of the left side of the prompt without ansi color + left_prompt_width: usize, + // The string for the right side of the prompt + right_prompt: String, + // The length of the right side of the prompt without ansi color + right_print_width: usize, + // The size of the terminal in columns, rows + terminal_size: (u16, u16), + // The number of line buffer character space between the + // the left prompt and the right prompt. When this encroaches + // into the right side prompt, we should not show the right + // prompt. For future use. Not used currently. + max_length: i32, +} + +impl Prompt { + pub fn new() -> Prompt { + let term_size = get_terminal_size(); + // create the initial prompt + let p_indicator = "〉".to_string(); + let l_prompt = "".to_string(); + let r_prompt = get_now(); + + Prompt { + // a clone for you, and a clone for you, and a clone for you... + prompt_indicator: p_indicator.clone(), + left_prompt: l_prompt.clone(), + left_prompt_width: p_indicator.chars().count() + l_prompt.chars().count(), + right_prompt: r_prompt.clone(), + right_print_width: r_prompt.chars().count(), + max_length: -1, + terminal_size: term_size, + } + } + + pub fn set_left_prompt(&mut self, lprompt: String) { + self.left_prompt = lprompt; + } + + pub fn set_right_prompt(&mut self, rprompt: String) { + self.right_prompt = rprompt; + } + + pub fn set_prompt_indicator(&mut self, indicator: String) { + self.prompt_indicator = indicator; + } + + pub fn print_prompt(&mut self) -> String { + // This is not really a left and right prompt at all. It's faking it. + // It's really just a string that fits within the width of your screen. + + let mut prompt_str = String::new(); + + self.terminal_size = get_terminal_size(); + let working_dir = match get_working_dir() { + Ok(cwd) => cwd, + _ => "no path".to_string(), + }; + + self.set_left_prompt(working_dir); + self.left_prompt_width = self.left_prompt.chars().count(); + prompt_str.push_str(&self.left_prompt); + + // Figure out the right side padding width + let padding_width: usize = if usize::from(self.terminal_size.0) < self.left_prompt_width { + 0 + } else { + usize::from(self.terminal_size.0) - self.left_prompt_width + }; + + let right = format!("{:>width$}", get_now(), width = padding_width); + self.set_right_prompt(right); + self.right_print_width = self.right_prompt.chars().count(); + + // At some point check the buffer length, assuming an actual left & right propmt functionality + self.max_length = -1; + + prompt_str.push_str(&self.right_prompt); + prompt_str.push_str(&self.prompt_indicator); + + prompt_str + } +} + +fn get_terminal_size() -> (u16, u16) { + let ts = terminal::size(); + match ts { + Ok((columns, rows)) => (columns, rows), + Err(_) => (0, 0), + } +} + +fn get_working_dir() -> Result { + let path = env::current_dir()?; + Ok(path.display().to_string()) +} + +fn get_now() -> String { + let now = Local::now(); + format!("{}", now.format("%m/%d/%Y %I:%M:%S %p")) +}