From e2acef5ddfa5c7bf470aee5e24f429eabfb17951 Mon Sep 17 00:00:00 2001 From: rabite Date: Fri, 1 Mar 2019 13:56:57 +0100 Subject: minibuffer history and more keybinds --- src/fail.rs | 4 + src/minibuffer.rs | 303 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 232 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/fail.rs b/src/fail.rs index 605a4ef..ff9ad2e 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -37,6 +37,10 @@ pub enum HError { PopupFinnished, #[fail(display = "Input finnished")] InputFinnished, + #[fail(display = "No completions found")] + NoCompletionsError, + #[fail(display = "No more history")] + NoHistoryError } impl From for HError { diff --git a/src/minibuffer.rs b/src/minibuffer.rs index 2533014..985fbd5 100644 --- a/src/minibuffer.rs +++ b/src/minibuffer.rs @@ -10,9 +10,11 @@ pub struct MiniBuffer { coordinates: Coordinates, query: String, input: String, - done: bool, position: usize, - history: Vec + history: Vec, + history_pos: Option, + completions: Vec, + last_completion: Option } impl MiniBuffer { @@ -24,41 +26,195 @@ impl MiniBuffer { coordinates: coordinates, query: String::new(), input: String::new(), - done: false, position: 0, - history: vec![] + history: vec![], + history_pos: None, + completions: vec![], + last_completion: None } } pub fn query(&mut self, query: &str) -> HResult { self.query = query.to_string(); self.input.clear(); - self.done = false; self.position = 0; + self.history_pos = None; + self.completions.clear(); + self.last_completion = None; write!(stdout(), "{}", termion::cursor::Show)?; self.popup()?; + Ok(self.input.clone()) + } - // for event in stdin().events() { - // let event = event?; - // self.on_event(event); - // if self.done { - // break - // } - // self.draw()?; + pub fn complete(&mut self) -> HResult<()> { + if !self.input.ends_with(" ") { + if !self.completions.is_empty() { + self.cycle_completions()?; + return Ok(()); + } - // write!(stdout(), "{}", termion::cursor::Restore)?; - // if self.position != 0 { - // write!(stdout(), - // "{}", - // termion::cursor::Right(self.position as u16))?; - // } - // stdout().flush()?; - // } + let part = self.input + .rsplitn(2, " ") + .take(1) + .map(|s| s.to_string()) + .collect::(); + let completions = find_files(part.clone()); - Ok(self.input.clone()) + if let Ok(mut completions) = completions { + let completion = completions.pop()?; + + self.input + = self.input[..self.input.len() - part.len()].to_string(); + self.input.push_str(&completion); + self.position += &completion.len() - part.len(); + + self.last_completion = Some(completion); + self.completions = completions; + } else { + let completions = find_bins(&part); + + if let Ok(mut completions) = completions { + let completion = completions.pop()?; + + self.input = self.input[..self.input.len() + - part.len()].to_string(); + self.input.push_str(&completion); + self.position += &completion.len() - part.len(); + + self.last_completion = Some(completion); + self.completions = completions; + } + } + } else { + self.input += "$s"; + self.position += 2 + } + Ok(()) + } + + pub fn cycle_completions(&mut self) -> HResult<()> { + let last_comp = self.last_completion.as_ref()?; + let last_len = last_comp.len(); + + self.input = self.input.trim_end_matches(last_comp).to_string(); + self.position -= last_len; + + let next_comp = self.completions.pop()?; + self.input.push_str(&next_comp); + self.position += next_comp.len(); + self.last_completion = Some(next_comp); + Ok(()) + } + + pub fn history_up(&mut self) -> HResult<()> { + if self.history_pos == Some(0) { self.history_pos = None; } + if self.history.len() == 0 { return Err(HError::NoHistoryError); } + + if let Some(history_pos) = self.history_pos { + let historic = self.history[history_pos - 1].clone(); + + self.input = historic; + self.position = self.input.len(); + self.history_pos = Some(history_pos - 1); + } else { + let historic = self.history[self.history.len() - 1].clone(); + + self.input = historic; + self.position = self.input.len(); + self.history_pos = Some(self.history.len() - 1); + } + Ok(()) + } + + pub fn history_down(&mut self) -> HResult<()> { + let hist_len = self.history.len(); + + if hist_len == 0 { return Err(HError::NoHistoryError); } + if self.history_pos == Some(hist_len) || + self.history_pos == None + { self.history_pos = Some(0); } + + if let Some(history_pos) = self.history_pos { + let historic = self.history[history_pos].clone(); + + self.input = historic; + self.position = self.input.len(); + self.history_pos = Some(history_pos + 1); + } else { + let historic = self.history[0].clone(); + + self.input = historic; + self.position = self.input.len(); + self.history_pos = Some(1); + } + Ok(()) + } + + pub fn clear_line(&mut self) -> HResult<()> { + self.input.clear(); + self.position = 0; + Ok(()) + } + + pub fn delete_word(&mut self) -> HResult<()> { + let old_input_len = self.input.len(); + let (before_cursor, after_cursor) = self.input.split_at(self.position); + + let no_trim_len = before_cursor.len(); + let before_cursor = before_cursor.trim_end(); + + if no_trim_len != before_cursor.len() { + self.position -= no_trim_len - before_cursor.len(); + self.input = before_cursor.to_string() + after_cursor; + return Ok(()); + } + + if before_cursor.ends_with("/") { + let mut new_input = before_cursor.to_string(); + new_input.pop(); + self.input = new_input + after_cursor; + self.position -= 1; + return Ok(()); + }; + + let dir_boundary = before_cursor.rfind("/"); + let word_boundary = before_cursor.rfind(" "); + let boundaries = (dir_boundary, word_boundary); + + let new_input = match boundaries { + (Some(dir_boundary), Some(word_boundary)) => { + if dir_boundary > word_boundary { + before_cursor + .split_at(dir_boundary).0 + .to_string() + "/" + } else { + before_cursor + .split_at(word_boundary).0 + .to_string() + " " + } + } + (Some(dir_boundary), None) => { + before_cursor + .split_at(dir_boundary).0 + .to_string() + "/" + } + (None, Some(word_boundary)) => { + before_cursor + .split_at(word_boundary).0 + .to_string() + " " + } + (None, None) => "".to_string() + } + after_cursor; + + let len_difference = old_input_len - new_input.len(); + self.position -= len_difference; + + self.input = new_input; + + Ok(()) } pub fn input_finnished(&mut self) -> HResult<()> { @@ -66,54 +222,64 @@ impl MiniBuffer { } } -pub fn find_bins(comp_name: &str) -> Vec { - let paths = std::env::var_os("PATH").unwrap() +pub fn find_bins(comp_name: &str) -> HResult> { + let paths = std::env::var_os("PATH")? .to_string_lossy() .split(":") .map(|s| s.to_string()) .collect::>(); - paths.iter().map(|path| { - std::fs::read_dir(path).unwrap().flat_map(|file| { - let file = file.unwrap(); - let name = file.file_name().into_string().unwrap(); - if name.starts_with(comp_name) { - Some(name) - } else { - None - } - }).collect::>() - }).flatten().collect::>() + let completions = paths.iter().map(|path| { + if let Ok(read_dir) = std::fs::read_dir(path) { + read_dir.map(|file| { + let file = file.unwrap(); + let name = file.file_name().into_string().unwrap(); + if name.starts_with(comp_name) { + Ok(name) + } else { + Err(HError::NoCompletionsError) + } + }).collect::>>() + } else { vec![Err(HError::NoCompletionsError)] } + }).flatten() + .filter(|result| result.is_ok()) + .map(|result| result.unwrap()) + .collect::>(); + if completions.is_empty() { return Err(HError::NoCompletionsError); } + Ok(completions) } -pub fn find_files(mut comp_name: String) -> Vec { - let mut path = std::path::PathBuf::from(&comp_name); +pub fn find_files(comp_name: String) -> HResult> { + let mut path = std::env::current_dir().unwrap(); + let comp_path = std::path::PathBuf::from(&comp_name); + path.push(&comp_path); + + let filename_part = path.file_name()?.to_string_lossy().to_string(); + + let dir = if path.is_dir() { &path } else { path.parent().unwrap() }; + let dir = std::path::PathBuf::from(dir); - let dir = if comp_name.starts_with("/") { - comp_name = path.file_name().unwrap().to_string_lossy().to_string(); - path.pop(); - path.to_string_lossy().to_string() - } else { - std::env::current_dir().unwrap().to_string_lossy().to_string() - }; + let prefix = comp_name.trim_end_matches(&filename_part); - let reader = std::fs::read_dir(dir.clone()); - if reader.is_err() { return vec![] } - let reader = reader.unwrap(); + let reader = std::fs::read_dir(&dir)?; - reader.flat_map(|file| { - let file = file.unwrap(); + let completions = reader.map(|file| { + let file = file?; let name = file.file_name().into_string().unwrap(); - if name.starts_with(&comp_name) { + if name.starts_with(&filename_part) { if file.file_type().unwrap().is_dir() { - Some(format!("{}/{}/", &dir, name)) + Ok(format!("{}{}/", prefix, name)) } else { - Some(format!("/{}/", name)) + Ok(format!("{}{}", prefix, name)) } } else { - None + Err(HError::NoCompletionsError) } - }).collect::>() + }).filter(|res| res.is_ok() ) + .map(|res| res.unwrap() ) + .collect::>(); + if completions.is_empty() { return Err(HError::NoCompletionsError); } + Ok(completions) } impl Widget for MiniBuffer { @@ -149,28 +315,7 @@ impl Widget for MiniBuffer { self.input_finnished()?; } Key::Char('\t') => { - if !self.input.ends_with(" ") { - let part = self.input.rsplitn(2, " ").take(1) - .map(|s| s.to_string()).collect::(); - let completions = find_files(part.clone()); - if !completions.is_empty() { - self.input - = self.input[..self.input.len() - part.len()].to_string(); - self.input.push_str(&completions[0]); - self.position += &completions[0].len() - part.len(); - } else { - let completions = find_bins(&part); - if !completions.is_empty() { - self.input = self.input[..self.input.len() - - part.len()].to_string(); - self.input.push_str(&completions[0]); - self.position += &completions[0].len() - part.len(); - } - } - } else { - self.input += "$s"; - self.position += 2 - } + self.complete()?; } Key::Backspace => { if self.position != 0 { @@ -193,6 +338,14 @@ impl Widget for MiniBuffer { self.position += 1; } } + Key::Up | Key::Ctrl('p') | Key::Alt('p') => { + self.history_up()?; + } + Key::Down | Key::Ctrl('n') | Key::Alt('n') => { + self.history_down()?; + } + Key::Ctrl('u') => { self.clear_line()?; }, + Key::Ctrl('h') => { self.delete_word()?; }, Key::Ctrl('a') => { self.position = 0 }, Key::Ctrl('e') => { self.position = self.input.len(); }, Key::Char(key) => { -- cgit v1.2.3