diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/commands/command_line.rs | 5 | ||||
-rw-r--r-- | src/commands/open_file.rs | 4 | ||||
-rw-r--r-- | src/commands/set_mode.rs | 5 | ||||
-rw-r--r-- | src/context/commandline_context.rs | 10 | ||||
-rw-r--r-- | src/ui/views/tui_textfield.rs | 132 | ||||
-rw-r--r-- | src/ui/widgets/tui_topbar.rs | 13 |
6 files changed, 121 insertions, 48 deletions
diff --git a/src/commands/command_line.rs b/src/commands/command_line.rs index 9c5aa70..608e5dd 100644 --- a/src/commands/command_line.rs +++ b/src/commands/command_line.rs @@ -4,7 +4,7 @@ use crate::config::AppKeyMapping; use crate::context::AppContext; use crate::error::JoshutoResult; use crate::key_command::{AppExecute, Command}; -use crate::ui::views::TuiTextField; +use crate::ui::views::{DummyListener, TuiTextField}; use crate::ui::AppBackend; pub fn read_and_execute( @@ -15,11 +15,12 @@ pub fn read_and_execute( suffix: &str, ) -> JoshutoResult { context.flush_event(); + let mut listener = DummyListener {}; let user_input: Option<String> = TuiTextField::default() .prompt(":") .prefix(prefix) .suffix(suffix) - .get_input(backend, context); + .get_input(backend, context, &mut listener); if let Some(s) = user_input { let trimmed = s.trim_start(); diff --git a/src/commands/open_file.rs b/src/commands/open_file.rs index eaa94ef..ba71b12 100644 --- a/src/commands/open_file.rs +++ b/src/commands/open_file.rs @@ -5,6 +5,7 @@ use crate::commands::{quit, reload}; use crate::config::ProgramEntry; use crate::context::AppContext; use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; +use crate::ui::views::DummyListener; use crate::ui::views::TuiTextField; use crate::ui::AppBackend; use crate::util::mimetype::get_mimetype; @@ -99,11 +100,12 @@ where .map(|(i, e)| format!(" {} | {}", i, e)) .collect(); + let mut listener = DummyListener {}; TuiTextField::default() .prompt(":") .prefix(PROMPT) .menu_items(menu_options.iter().map(|s| s.as_str())) - .get_input(backend, context) + .get_input(backend, context, &mut listener) }; match user_input.as_ref() { Some(user_input) if user_input.starts_with(PROMPT) => { diff --git a/src/commands/set_mode.rs b/src/commands/set_mode.rs index 53c8740..1f11462 100644 --- a/src/commands/set_mode.rs +++ b/src/commands/set_mode.rs @@ -2,7 +2,7 @@ use std::fs; use crate::context::AppContext; use crate::error::JoshutoResult; -use crate::ui::views::TuiTextField; +use crate::ui::views::{DummyListener, TuiTextField}; use crate::ui::AppBackend; use crate::util::unix; @@ -51,13 +51,14 @@ pub fn set_mode(context: &mut AppContext, backend: &mut AppBackend) -> JoshutoRe Some(entry) => { let mode = entry.metadata.permissions_ref().mode(); let mode_string = unix::mode_to_string(mode); + let mut listener = DummyListener {}; context.flush_event(); TuiTextField::default() .prompt(":") .prefix(PREFIX) .suffix(&mode_string.as_str()[1..]) - .get_input(backend, context) + .get_input(backend, context, &mut listener) } None => None, }; diff --git a/src/context/commandline_context.rs b/src/context/commandline_context.rs index 5169029..f9d262d 100644 --- a/src/context/commandline_context.rs +++ b/src/context/commandline_context.rs @@ -1,13 +1,13 @@ -use rustyline::history; +use rustyline::history::{History, MemHistory}; pub struct CommandLineContext { - history: history::History, + history: MemHistory, } impl std::default::Default for CommandLineContext { fn default() -> Self { Self { - history: history::History::new(), + history: MemHistory::new(), } } } @@ -17,10 +17,10 @@ impl CommandLineContext { Self::default() } - pub fn history_ref(&self) -> &history::History { + pub fn history_ref(&self) -> &dyn History { &self.history } - pub fn history_mut(&mut self) -> &mut history::History { + pub fn history_mut(&mut self) -> &mut dyn History { &mut self.history } } diff --git a/src/ui/views/tui_textfield.rs b/src/ui/views/tui_textfield.rs index bc57138..ba783fd 100644 --- a/src/ui/views/tui_textfield.rs +++ b/src/ui/views/tui_textfield.rs @@ -1,10 +1,9 @@ use std::str::FromStr; use rustyline::completion::{Candidate, Completer, FilenameCompleter, Pair}; -use rustyline::{ - line_buffer::{self, LineBuffer}, - At, Word, -}; +use rustyline::history::{History, SearchDirection}; +use rustyline::line_buffer::{self, ChangeListener, DeleteListener, Direction, LineBuffer}; +use rustyline::{At, Changeset, Word}; use termion::event::{Event, Key}; use tui::layout::Rect; @@ -19,6 +18,22 @@ use crate::ui::views::TuiView; use crate::ui::widgets::{TuiMenu, TuiMultilineText}; use crate::ui::AppBackend; +// Might need to be implemented in the future +#[derive(Clone, Debug)] +pub struct DummyListener {} + +impl DeleteListener for DummyListener { + fn delete(&mut self, idx: usize, string: &str, dir: Direction) {} +} + +impl ChangeListener for DummyListener { + fn insert_char(&mut self, idx: usize, c: char) {} + + fn insert_str(&mut self, idx: usize, string: &str) {} + + fn replace(&mut self, idx: usize, old: &str, new: &str) {} +} + struct CompletionTracker { pub index: usize, pub pos: usize, @@ -78,6 +93,7 @@ impl<'a> TuiTextField<'a> { &mut self, backend: &mut AppBackend, context: &mut AppContext, + listener: &mut DummyListener, ) -> Option<String> { let mut line_buffer = line_buffer::LineBuffer::with_capacity(255); let completer = FilenameCompleter::new(); @@ -86,8 +102,8 @@ impl<'a> TuiTextField<'a> { let char_idx = self._prefix.chars().map(|c| c.len_utf8()).sum(); - line_buffer.insert_str(0, self._prefix); - line_buffer.insert_str(line_buffer.len(), self._suffix); + line_buffer.insert_str(0, self._prefix, listener); + line_buffer.insert_str(line_buffer.len(), self._suffix, listener); line_buffer.set_pos(char_idx); let terminal = backend.terminal_mut(); @@ -169,26 +185,26 @@ impl<'a> TuiTextField<'a> { AppEvent::Termion(Event::Key(key)) => { let dirty = match key { Key::Backspace => { - let res = line_buffer.backspace(1); + let res = line_buffer.backspace(1, listener); if let Ok(command) = Command::from_str(line_buffer.as_str()) { command.interactive_execute(context) } res } - Key::Delete => line_buffer.delete(1).is_some(), + Key::Delete => line_buffer.delete(1, listener).is_some(), Key::Home => line_buffer.move_home(), Key::End => line_buffer.move_end(), Key::Up => { curr_history_index = curr_history_index.saturating_sub(1); line_buffer.move_home(); - line_buffer.kill_line(); - if let Some(s) = context + line_buffer.kill_line(listener); + if let Ok(Some(s)) = context .commandline_context_ref() .history_ref() - .get(curr_history_index) + .get(curr_history_index, SearchDirection::Forward) { - line_buffer.insert_str(0, s); + line_buffer.insert_str(0, &s.entry, listener); } true } @@ -201,13 +217,13 @@ impl<'a> TuiTextField<'a> { curr_history_index }; line_buffer.move_home(); - line_buffer.kill_line(); - if let Some(s) = context + line_buffer.kill_line(listener); + if let Ok(Some(s)) = context .commandline_context_ref() .history_ref() - .get(curr_history_index) + .get(curr_history_index, SearchDirection::Reverse) { - line_buffer.insert_str(0, s); + line_buffer.insert_str(0, &s.entry, listener); } true } @@ -215,17 +231,17 @@ impl<'a> TuiTextField<'a> { let _ = terminal.hide_cursor(); return None; } - Key::Char('\t') => autocomplete( + Key::Char('\t') => autocomplete_forward( &mut line_buffer, &mut completion_tracker, &completer, - false, + listener, ), - Key::BackTab => autocomplete( + Key::BackTab => autocomplete_backwards( &mut line_buffer, &mut completion_tracker, &completer, - true, + listener, ), // Current `completion_tracker` should be dropped @@ -261,14 +277,14 @@ impl<'a> TuiTextField<'a> { }) } - Key::Ctrl('w') => line_buffer.delete_prev_word(Word::Vi, 1), - Key::Ctrl('u') => line_buffer.discard_line(), - Key::Ctrl('d') => line_buffer.delete(1).is_some(), + Key::Ctrl('w') => line_buffer.delete_prev_word(Word::Vi, 1, listener), + Key::Ctrl('u') => line_buffer.discard_line(listener), + Key::Ctrl('d') => line_buffer.delete(1, listener).is_some(), Key::Char('\n') => { break; } Key::Char(c) => { - let dirty = line_buffer.insert(c, 1).is_some(); + let dirty = line_buffer.insert(c, 1, listener).is_some(); if let Ok(command) = Command::from_str(line_buffer.as_str()) { command.interactive_execute(context) @@ -300,27 +316,71 @@ impl<'a> TuiTextField<'a> { } } -fn autocomplete( +fn autocomplete_forward( line_buffer: &mut LineBuffer, completion_tracker: &mut Option<CompletionTracker>, completer: &FilenameCompleter, - reversed: bool, + listener: &mut DummyListener, ) -> bool { // If we are in the middle of a word, move to the end of it, // so we don't split it with autocompletion. move_to_the_end(line_buffer); if let Some(ref mut ct) = completion_tracker { - ct.index = if reversed { - ct.index.checked_sub(1).unwrap_or(ct.candidates.len() - 1) - } else if ct.index + 1 < ct.candidates.len() { - ct.index + 1 - } else { - ct.index - }; + if ct.index + 1 >= ct.candidates.len() { + return false; + } + ct.index = ct.index + 1; + + let candidate = &ct.candidates[ct.index]; + + let pos = ct.pos; + let first = candidate.display(); + + line_buffer.set_pos(pos); + line_buffer.kill_buffer(listener); + line_buffer.insert_str(pos, &first, listener); + line_buffer.move_end(); + } else if let Some((pos, mut candidates)) = get_candidates(completer, line_buffer) { + if !candidates.is_empty() { + candidates.sort_by(|x, y| { + x.display() + .partial_cmp(y.display()) + .unwrap_or(std::cmp::Ordering::Less) + }); + + let first = candidates[0].display().to_string(); + let mut ct = + CompletionTracker::new(pos, candidates, String::from(line_buffer.as_str())); + ct.index = 0; + + *completion_tracker = Some(ct); + + line_buffer.set_pos(pos); + line_buffer.kill_buffer(listener); + line_buffer.insert_str(pos, &first, listener); + line_buffer.move_end(); + } + } + + false +} + +fn autocomplete_backwards( + line_buffer: &mut LineBuffer, + completion_tracker: &mut Option<CompletionTracker>, + completer: &FilenameCompleter, + listener: &mut DummyListener, +) -> bool { + // If we are in the middle of a word, move to the end of it, + // so we don't split it with autocompletion. + move_to_the_end(line_buffer); + + if let Some(ref mut ct) = completion_tracker { + ct.index = ct.index.checked_sub(1).unwrap_or(ct.candidates.len() - 1); let candidate = &ct.candidates[ct.index]; - completer.update(line_buffer, ct.pos, candidate.display()); + line_buffer.update(candidate.display(), ct.pos, listener); } else if let Some((pos, mut candidates)) = get_candidates(completer, line_buffer) { if !candidates.is_empty() { candidates.sort_by(|x, y| { @@ -329,7 +389,7 @@ fn autocomplete( .unwrap_or(std::cmp::Ordering::Less) }); - let first_idx = if reversed { candidates.len() - 1 } else { 0 }; + let first_idx = candidates.len() - 1; let first = candidates[first_idx].display().to_string(); let mut ct = @@ -337,7 +397,7 @@ fn autocomplete( ct.index = first_idx; *completion_tracker = Some(ct); - completer.update(line_buffer, pos, &first); + line_buffer.update(&first, pos, listener); } } diff --git a/src/ui/widgets/tui_topbar.rs b/src/ui/widgets/tui_topbar.rs index 1f32ace..d98fd31 100644 --- a/src/ui/widgets/tui_topbar.rs +++ b/src/ui/widgets/tui_topbar.rs @@ -32,10 +32,19 @@ impl<'a> Widget for TuiTopBar<'a> { let mut ellipses = None; let mut curr_path_str = self.path.to_string_lossy().into_owned(); - if curr_path_str.width() > area.width as usize { + let num_tabs = self.context.tab_context_ref().len(); + let tab_width = num_tabs * 8; + let name_width = USERNAME.as_str().len() + HOSTNAME.as_str().len() + 2; + + if tab_width + name_width > area.width as usize { + curr_path_str = "".to_owned(); + } else if curr_path_str.width() > area.width as usize - tab_width - name_width { if let Some(s) = self.path.file_name() { let mut short_path = String::new(); - for component in self.path.components() { + let mut components: Vec<Component> = self.path.components().collect(); + components.pop(); + + for component in components { match component { Component::RootDir => short_path.push('/'), Component::Normal(s) => { |