Skip to content

Commit

Permalink
Adding history navigation control and typing after cursor move
Browse files Browse the repository at this point in the history
  • Loading branch information
deven96 committed Oct 4, 2024
1 parent 3b71a19 commit cdf31a8
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 7 deletions.
49 changes: 49 additions & 0 deletions ahnlich/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 ahnlich/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ opentelemetry = { version = "0.23.0", features = ["trace"] }
tracing-opentelemetry = "0.24.0"
log = "0.4"
fallible_collections = "0.4.9"
dirs = "5.0.1"
1 change: 1 addition & 0 deletions ahnlich/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ path = "src/lib.rs"
[dependencies]
crossterm = { version = "0.28.1", feature = ["bracketed-paste"]}
clap.workspace = true
dirs.workspace = true
dsl = { path = "../dsl", version = "*" }
thiserror.workspace = true
tokio.workspace = true
Expand Down
103 changes: 103 additions & 0 deletions ahnlich/cli/src/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::{
fs::OpenOptions,
io::{self, BufRead, Write},
path::PathBuf,
};

fn get_history_file_path() -> PathBuf {
let mut path = dirs::home_dir().expect("Could not find home directory");
path.push(".ahnlich_cli_history");
path
}

fn load_command_history() -> Vec<String> {
let path = get_history_file_path();
if path.exists() {
let file = OpenOptions::new()
.read(true)
.open(path)
.expect("Unable to open history file");
let reader = io::BufReader::new(file);
reader.lines().filter_map(Result::ok).collect()
} else {
Vec::new()
}
}

fn save_command_history(commands: &[String]) {
let path = get_history_file_path();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(path)
.expect("Unable to open history file");
for command in commands {
writeln!(file, "{}", command).expect("Unable to write to history file");
}
}

pub(crate) struct HistoryManager {
command_history: Vec<String>,
current_command_index: usize,
}

impl HistoryManager {
pub(crate) fn new() -> Self {
let command_history = load_command_history();
let current_command_index = command_history.len();
Self {
command_history,
current_command_index,
}
}

pub(crate) fn down(&mut self) -> String {
if self.current_command_index < self.command_history.len() {
self.current_command_index += 1;
}
if self.is_index_end() {
String::new()
} else {
self.get_at_index()
}
}

pub(crate) fn up(&mut self) -> String {
if self.current_command_index > 0 {
self.current_command_index -= 1;
}
if self.command_history.is_empty() && self.current_command_index == 0 {
String::new()
} else {
self.get_at_index()
}
}

fn get_at_index(&self) -> String {
self.command_history[self.current_command_index].clone()
}

pub(crate) fn reset_index(&mut self) {
self.current_command_index = self.command_history.len();
}

pub(crate) fn is_index_end(&self) -> bool {
self.current_command_index == self.command_history.len()
}

pub(crate) fn save_to_disk(&self) {
save_command_history(&self.command_history);
}

pub(crate) fn add_command(&mut self, command: &str) {
if let Some(last_command) = self.command_history.last() {
if last_command != command {
self.command_history.push(command.to_string());
}
} else {
self.command_history.push(command.to_string());
}
self.reset_index();
}
}
1 change: 1 addition & 0 deletions ahnlich/cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod config;
pub mod connect;
mod history;
pub mod term;
50 changes: 44 additions & 6 deletions ahnlich/cli/src/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crossterm::{
};
use std::io::{self, stdout, Stdout, Write};

use crate::connect::AgentPool;
use crate::{connect::AgentPool, history::HistoryManager};

#[derive(Debug)]
enum SpecialEntry {
Expand All @@ -29,6 +29,15 @@ enum Entry {
None,
}

impl Entry {
fn is_history_key(&self) -> bool {
matches!(
self,
Entry::Special(SpecialEntry::Up) | Entry::Special(SpecialEntry::Down)
)
}
}

#[derive(Debug)]
enum LineResult {
Command(String),
Expand Down Expand Up @@ -136,6 +145,7 @@ impl Term {
queue!(
stdout,
cursor::MoveToColumn(col_pos),
terminal::Clear(terminal::ClearType::FromCursorDown),
Print(formatted_output)
)?;
stdout.flush()?;
Expand All @@ -161,21 +171,45 @@ impl Term {
Ok(())
}

fn read_line(&self, stdout: &mut Stdout) -> io::Result<LineResult> {
fn read_line(
&self,
stdout: &mut Stdout,
history: &mut HistoryManager,
) -> io::Result<LineResult> {
let (start_pos_col, _) = cursor::position()?;
let mut output = String::new();

loop {
let char = self.read_char()?;
let (current_pos_col, _) = cursor::position()?;
if !char.is_history_key() {
history.reset_index();
}
match char {
Entry::Char(c) => {
output.push(c);
let insertion_position = current_pos_col - start_pos_col;
output.insert(insertion_position as usize, c);
self.move_to_pos_and_print(stdout, &output, start_pos_col)?;
stdout.execute(cursor::MoveToColumn(current_pos_col + 1))?;
}
Entry::Special(special) => match special {
SpecialEntry::Up | SpecialEntry::Down => {
continue;
SpecialEntry::Up => {
output = history.up();
queue!(
stdout,
cursor::MoveToColumn(start_pos_col),
terminal::Clear(terminal::ClearType::FromCursorDown),
)?;
self.move_to_pos_and_print(stdout, &output, start_pos_col)?;
}
SpecialEntry::Down => {
output = history.down();
queue!(
stdout,
cursor::MoveToColumn(start_pos_col),
terminal::Clear(terminal::ClearType::FromCursorDown),
)?;
self.move_to_pos_and_print(stdout, &output, start_pos_col)?;
}
SpecialEntry::Enter => {
queue!(stdout, Print("\n"), cursor::MoveToColumn(0))?;
Expand Down Expand Up @@ -220,6 +254,8 @@ impl Term {
}
}
}
history.add_command(&output);
history.save_to_disk();
Ok(LineResult::Command(output))
}

Expand All @@ -229,9 +265,11 @@ impl Term {
stdout.execute(cursor::EnableBlinking)?;
stdout.execute(cursor::SetCursorStyle::BlinkingBar)?;

let mut history = HistoryManager::new();

loop {
self.ahnlich_prompt(&mut stdout)?;
let input = self.read_line(&mut stdout)?;
let input = self.read_line(&mut stdout, &mut history)?;
match input {
LineResult::Exit => {
break;
Expand Down
2 changes: 1 addition & 1 deletion ahnlich/dsl/src/syntax/syntax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ info_server = { whitespace* ~ ^"infoserver" ~ whitespace* ~ !(ASCII_ALPHANUMERIC
list_stores = { whitespace* ~ ^"liststores" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)}
list_clients = { whitespace* ~ ^"listclients" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)}
purge_stores = { whitespace* ~ ^"purgestores" ~ whitespace* ~ !(ASCII_ALPHANUMERIC)}
drop_store = { whitespace* ~ ^"dropstore" ~ whitespace* ~ store_name ~ (if_exists)? ~ !(ASCII_ALPHANUMERIC)}
drop_store = { whitespace* ~ ^"dropstore" ~ whitespace* ~ store_name ~ (if_exists | invalid_statement)?}
create_pred_index = { whitespace* ~ ^"createpredindex" ~ whitespace* ~ "(" ~ index_names ~ ")" ~ in_ignored ~ store_name }
create_non_linear_algorithm_index = { whitespace* ~ ^"createnonlinearalgorithmindex" ~ whitespace* ~ "(" ~ non_linear_algorithms ~ ")" ~ in_ignored ~ store_name}
drop_pred_index = { whitespace* ~ ^"droppredindex" ~ whitespace* ~ (if_exists)? ~ "(" ~ index_names ~ ")" ~ in_ignored ~ store_name }
Expand Down

0 comments on commit cdf31a8

Please sign in to comment.