Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ edition = "2018"
[dependencies]
crossterm = "0.19.0"
unicode-segmentation = "1.7.1"
chrono = "0.4.19"
53 changes: 36 additions & 17 deletions src/engine.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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:
//
Expand All @@ -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))?;
Expand All @@ -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 "
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)?;
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ use diagnostic::print_events;

mod history_search;

mod prompt;
use prompt::Prompt;

fn main() -> Result<()> {
let mut stdout = stdout();

Expand Down
110 changes: 110 additions & 0 deletions src/prompt.rs
Original file line number Diff line number Diff line change
@@ -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<String, std::io::Error> {
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"))
}