summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/command_line.rs5
-rw-r--r--src/commands/open_file.rs4
-rw-r--r--src/commands/set_mode.rs5
-rw-r--r--src/context/commandline_context.rs10
-rw-r--r--src/ui/views/tui_textfield.rs132
-rw-r--r--src/ui/widgets/tui_topbar.rs13
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) => {