pull/154/merge
sebashwa 1 year ago committed by GitHub
commit 1761910877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -173,10 +173,10 @@ impl App {
&database,
&table,
0,
if self.record_table.filter.input_str().is_empty() {
if self.record_table.filter.input.value_str().is_empty() {
None
} else {
Some(self.record_table.filter.input_str())
Some(self.record_table.filter.input.value_str())
},
)
.await?;
@ -224,7 +224,7 @@ impl App {
return Ok(EventState::Consumed);
}
if key == self.config.key_config.enter && self.databases.tree_focused() {
if key == self.config.key_config.enter {
if let Some((database, table)) = self.databases.tree().selected_table() {
self.record_table.reset();
let (headers, records) = self
@ -279,10 +279,11 @@ impl App {
&database,
&table,
index as u16,
if self.record_table.filter.input_str().is_empty() {
if self.record_table.filter.input.value_str().is_empty()
{
None
} else {
Some(self.record_table.filter.input_str())
Some(self.record_table.filter.input.value_str())
},
)
.await?;

@ -1,5 +1,6 @@
use super::{compute_character_width, Component, DrawableComponent, EventState};
use super::{Component, DrawableComponent, EventState};
use crate::components::command::CommandInfo;
use crate::components::utils::input::Input;
use crate::event::Key;
use anyhow::Result;
use database_tree::Table;
@ -11,34 +12,23 @@ use tui::{
widgets::{Block, Borders, Paragraph},
Frame,
};
use unicode_width::UnicodeWidthStr;
pub struct DatabaseFilterComponent {
pub table: Option<Table>,
input: Vec<char>,
input_idx: usize,
input_cursor_position: u16,
pub input: Input,
}
impl DatabaseFilterComponent {
pub fn new() -> Self {
Self {
table: None,
input: Vec::new(),
input_idx: 0,
input_cursor_position: 0,
input: Input::new(),
}
}
pub fn input_str(&self) -> String {
self.input.iter().collect()
}
pub fn reset(&mut self) {
self.table = None;
self.input = Vec::new();
self.input_idx = 0;
self.input_cursor_position = 0;
self.input.reset();
}
}
@ -46,10 +36,10 @@ impl DrawableComponent for DatabaseFilterComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let query = Paragraph::new(Spans::from(format!(
"{:w$}",
if self.input.is_empty() && !focused {
if self.input.value.is_empty() && !focused {
"Filter tables".to_string()
} else {
self.input_str()
self.input.value_str()
},
w = area.width as usize
)))
@ -63,7 +53,7 @@ impl DrawableComponent for DatabaseFilterComponent {
if focused {
f.set_cursor(
(area.x + self.input_cursor_position).min(area.right().saturating_sub(1)),
(area.x + self.input.cursor_position).min(area.right().saturating_sub(1)),
area.y,
)
}
@ -75,58 +65,9 @@ impl Component for DatabaseFilterComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
match key {
Key::Char(c) => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position += compute_character_width(c);
return Ok(EventState::Consumed);
}
Key::Delete | Key::Backspace => {
if input_str.width() > 0 && !self.input.is_empty() && self.input_idx > 0 {
let last_c = self.input.remove(self.input_idx - 1);
self.input_idx -= 1;
self.input_cursor_position -= compute_character_width(last_c);
}
return Ok(EventState::Consumed);
}
Key::Left => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx -= 1;
self.input_cursor_position = self
.input_cursor_position
.saturating_sub(compute_character_width(self.input[self.input_idx]));
}
return Ok(EventState::Consumed);
}
Key::Ctrl('a') => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx = 0;
self.input_cursor_position = 0
}
return Ok(EventState::Consumed);
}
Key::Right => {
if self.input_idx < self.input.len() {
let next_c = self.input[self.input_idx];
self.input_idx += 1;
self.input_cursor_position += compute_character_width(next_c);
}
return Ok(EventState::Consumed);
}
Key::Ctrl('e') => {
if self.input_idx < self.input.len() {
self.input_idx = self.input.len();
self.input_cursor_position = self.input_str().width() as u16;
}
return Ok(EventState::Consumed);
}
_ => (),
match self.input.handle_key(key) {
(Some(_), _) => Ok(EventState::Consumed),
_ => Ok(EventState::NotConsumed),
}
Ok(EventState::NotConsumed)
}
}

@ -9,7 +9,7 @@ use crate::event::Key;
use crate::ui::common_nav;
use crate::ui::scrolllist::draw_list_block;
use anyhow::Result;
use database_tree::{Database, DatabaseTree, DatabaseTreeItem};
use database_tree::{Database, DatabaseTree, DatabaseTreeItem, MoveSelection};
use std::collections::BTreeSet;
use std::convert::From;
use tui::{
@ -36,7 +36,7 @@ pub enum Focus {
pub struct DatabasesComponent {
tree: DatabaseTree,
filter: DatabaseFilterComponent,
filterd_tree: Option<DatabaseTree>,
filtered_tree: Option<DatabaseTree>,
scroll: VerticalScroll,
focus: Focus,
key_config: KeyConfig,
@ -47,7 +47,7 @@ impl DatabasesComponent {
Self {
tree: DatabaseTree::default(),
filter: DatabaseFilterComponent::new(),
filterd_tree: None,
filtered_tree: None,
scroll: VerticalScroll::new(false, false),
focus: Focus::Tree,
key_config,
@ -63,7 +63,7 @@ impl DatabasesComponent {
None => pool.get_databases().await?,
};
self.tree = DatabaseTree::new(databases.as_slice(), &BTreeSet::new())?;
self.filterd_tree = None;
self.filtered_tree = None;
self.filter.reset();
Ok(())
}
@ -73,7 +73,22 @@ impl DatabasesComponent {
}
pub fn tree(&self) -> &DatabaseTree {
self.filterd_tree.as_ref().unwrap_or(&self.tree)
self.filtered_tree.as_ref().unwrap_or(&self.tree)
}
fn navigate_tree(&mut self, nav: MoveSelection) -> bool {
let tree = match self.filtered_tree.as_mut() {
Some(t) => t,
None => &mut self.tree,
};
tree.move_selection(nav)
}
fn maybe_navigate_tree(&mut self, key: Key) -> bool {
match common_nav(key, &self.key_config) {
Some(nav) => self.navigate_tree(nav),
None => false,
}
}
fn tree_item_to_span(
@ -168,10 +183,9 @@ impl DatabasesComponent {
.draw(f, chunks[0], matches!(self.focus, Focus::Filter))?;
let tree_height = chunks[1].height as usize;
let tree = if let Some(tree) = self.filterd_tree.as_ref() {
tree
} else {
&self.tree
let tree = match self.filtered_tree.as_ref() {
Some(t) => t,
None => &self.tree,
};
tree.visual_selection().map_or_else(
|| {
@ -190,10 +204,10 @@ impl DatabasesComponent {
item.clone(),
selected,
area.width,
if self.filter.input_str().is_empty() {
if self.filter.input.value_str().is_empty() {
None
} else {
Some(self.filter.input_str())
Some(self.filter.input.value_str())
},
)
});
@ -223,55 +237,51 @@ impl Component for DatabasesComponent {
}
fn event(&mut self, key: Key) -> Result<EventState> {
if key == self.key_config.filter && self.focus == Focus::Tree {
self.focus = Focus::Filter;
return Ok(EventState::Consumed);
}
if matches!(self.focus, Focus::Filter) {
self.filterd_tree = if self.filter.input_str().is_empty() {
None
} else {
Some(self.tree.filter(self.filter.input_str()))
};
}
match key {
Key::Enter if matches!(self.focus, Focus::Filter) => {
self.focus = Focus::Tree;
return Ok(EventState::Consumed);
}
key if matches!(self.focus, Focus::Filter) => {
if self.filter.event(key)?.is_consumed() {
match key {
Key::Esc => {
self.focus = Focus::Tree;
return Ok(EventState::Consumed);
}
}
key => {
if tree_nav(
if let Some(tree) = self.filterd_tree.as_mut() {
tree
} else {
&mut self.tree
},
key,
&self.key_config,
) {
Key::Ctrl('j') | Key::Ctrl('n') => {
self.navigate_tree(MoveSelection::Down);
return Ok(EventState::Consumed);
}
Key::Ctrl('k') | Key::Ctrl('p') => {
self.navigate_tree(MoveSelection::Up);
return Ok(EventState::Consumed);
}
key => {
if self.filter.event(key)?.is_consumed() {
let filter_str = self.filter.input.value_str();
self.filtered_tree = if filter_str.is_empty() {
None
} else {
Some(self.tree.filter(filter_str))
};
return Ok(EventState::Consumed);
}
}
}
} else if matches!(self.focus, Focus::Tree) {
match key {
key => {
if key == self.key_config.filter {
self.focus = Focus::Filter;
return Ok(EventState::Consumed);
}
if self.maybe_navigate_tree(key) {
return Ok(EventState::Consumed);
}
}
}
}
Ok(EventState::NotConsumed)
}
}
fn tree_nav(tree: &mut DatabaseTree, key: Key, key_config: &KeyConfig) -> bool {
if let Some(common_nav) = common_nav(key, key_config) {
tree.move_selection(common_nav)
} else {
false
}
}
#[cfg(test)]
mod test {
use super::{Color, Database, DatabaseTreeItem, DatabasesComponent, Span, Spans, Style};
@ -376,7 +386,7 @@ mod test {
}
#[test]
fn test_filterd_tree_item_to_span() {
fn test_filtered_tree_item_to_span() {
const WIDTH: u16 = 10;
assert_eq!(
DatabasesComponent::tree_item_to_span(

@ -3,6 +3,7 @@ use super::{
StatefulDrawableComponent, TableComponent,
};
use crate::components::command::CommandInfo;
use crate::components::utils::input::Input;
use crate::config::KeyConfig;
use crate::database::{ExecuteResult, Pool};
use crate::event::Key;
@ -34,9 +35,7 @@ pub enum Focus {
}
pub struct SqlEditorComponent {
input: Vec<char>,
input_cursor_position_x: u16,
input_idx: usize,
input: Input,
table: TableComponent,
query_result: Option<QueryResult>,
completion: CompletionComponent,
@ -48,9 +47,7 @@ pub struct SqlEditorComponent {
impl SqlEditorComponent {
pub fn new(key_config: KeyConfig) -> Self {
Self {
input: Vec::new(),
input_idx: 0,
input_cursor_position_x: 0,
input: Input::new(),
table: TableComponent::new(key_config.clone()),
completion: CompletionComponent::new(key_config.clone(), "", true),
focus: Focus::Editor,
@ -63,9 +60,10 @@ impl SqlEditorComponent {
fn update_completion(&mut self) {
let input = &self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i < &self.input_idx)
.filter(|(i, _)| i < &self.input.cursor_index)
.map(|(_, i)| i)
.collect::<String>()
.split(' ')
@ -80,16 +78,23 @@ impl SqlEditorComponent {
let mut input = Vec::new();
let first = self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i < &self.input_idx.saturating_sub(self.completion.word().len()))
.filter(|(i, _)| {
i < &self
.input
.cursor_index
.saturating_sub(self.completion.word().len())
})
.map(|(_, c)| c.to_string())
.collect::<Vec<String>>();
let last = self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i >= &self.input_idx)
.filter(|(i, _)| i >= &self.input.cursor_index)
.map(|(_, c)| c.to_string())
.collect::<Vec<String>>();
@ -113,21 +118,21 @@ impl SqlEditorComponent {
input.extend(middle.clone());
input.extend(last);
self.input = input.join("").chars().collect();
self.input_idx += &middle.len();
self.input.value = input.join("").chars().collect();
self.input.cursor_index += &middle.len();
if is_last_word {
self.input_idx += 1;
self.input.cursor_index += 1;
}
self.input_idx -= self.completion.word().len();
self.input_cursor_position_x += middle
self.input.cursor_index -= self.completion.word().len();
self.input.cursor_position += middle
.join("")
.chars()
.map(compute_character_width)
.sum::<u16>();
if is_last_word {
self.input_cursor_position_x += " ".to_string().width() as u16
self.input.cursor_position += " ".to_string().width() as u16
}
self.input_cursor_position_x -= self
self.input.cursor_position -= self
.completion
.word()
.chars()
@ -151,7 +156,7 @@ impl StatefulDrawableComponent for SqlEditorComponent {
})
.split(area);
let editor = StatefulParagraph::new(self.input.iter().collect::<String>())
let editor = StatefulParagraph::new(self.input.value.iter().collect::<String>())
.wrap(Wrap { trim: true })
.block(Block::default().borders(Borders::ALL));
@ -176,14 +181,10 @@ impl StatefulDrawableComponent for SqlEditorComponent {
if focused && matches!(self.focus, Focus::Editor) {
f.set_cursor(
(layout[0].x + 1)
.saturating_add(
self.input_cursor_position_x % layout[0].width.saturating_sub(2),
)
.saturating_add(self.input.cursor_position % layout[0].width.saturating_sub(2))
.min(area.right().saturating_sub(2)),
(layout[0].y
+ 1
+ self.input_cursor_position_x / layout[0].width.saturating_sub(2))
.min(layout[0].bottom()),
(layout[0].y + 1 + self.input.cursor_position / layout[0].width.saturating_sub(2))
.min(layout[0].bottom()),
)
}
@ -192,8 +193,8 @@ impl StatefulDrawableComponent for SqlEditorComponent {
f,
area,
false,
self.input_cursor_position_x % layout[0].width.saturating_sub(2) + 1,
self.input_cursor_position_x / layout[0].width.saturating_sub(2),
self.input.cursor_position % layout[0].width.saturating_sub(2) + 1,
self.input.cursor_position / layout[0].width.saturating_sub(2),
)?;
};
Ok(())
@ -205,62 +206,48 @@ impl Component for SqlEditorComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
if key == self.key_config.focus_above && matches!(self.focus, Focus::Table) {
self.focus = Focus::Editor
} else if key == self.key_config.enter {
return self.complete();
}
match key {
Key::Char(c) if matches!(self.focus, Focus::Editor) => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position_x += compute_character_width(c);
self.update_completion();
if matches!(self.focus, Focus::Table) {
return self.table.event(key);
}
return Ok(EventState::Consumed);
}
Key::Esc if matches!(self.focus, Focus::Editor) => self.focus = Focus::Table,
Key::Delete | Key::Backspace if matches!(self.focus, Focus::Editor) => {
if input_str.width() > 0 && !self.input.is_empty() && self.input_idx > 0 {
let last_c = self.input.remove(self.input_idx - 1);
self.input_idx -= 1;
self.input_cursor_position_x -= compute_character_width(last_c);
self.completion.update("");
}
if !matches!(self.focus, Focus::Editor) {
return Ok(EventState::NotConsumed);
}
return Ok(EventState::Consumed);
}
Key::Left if matches!(self.focus, Focus::Editor) => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx -= 1;
self.input_cursor_position_x = self
.input_cursor_position_x
.saturating_sub(compute_character_width(self.input[self.input_idx]));
self.completion.update("");
}
return Ok(EventState::Consumed);
}
Key::Right if matches!(self.focus, Focus::Editor) => {
if self.input_idx < self.input.len() {
let next_c = self.input[self.input_idx];
self.input_idx += 1;
self.input_cursor_position_x += compute_character_width(next_c);
self.completion.update("");
}
return Ok(EventState::Consumed);
if key == Key::Esc {
self.focus = Focus::Table;
return Ok(EventState::Consumed);
} else {
match self.input.handle_key(key) {
(Some(matched_key), input_updated) => match matched_key {
Key::Char(_) => {
self.update_completion();
return Ok(EventState::Consumed);
}
Key::Ctrl(_) => {
return Ok(EventState::Consumed);
}
_ => {
if input_updated {
self.completion.update("");
}
return Ok(EventState::Consumed);
}
},
_ => return Ok(EventState::NotConsumed),
}
key if matches!(self.focus, Focus::Table) => return self.table.event(key),
_ => (),
}
return Ok(EventState::NotConsumed);
}
async fn async_event(&mut self, key: Key, pool: &Box<dyn Pool>) -> Result<EventState> {
if key == self.key_config.enter && matches!(self.focus, Focus::Editor) {
let query = self.input.iter().collect();
let query = self.input.value.iter().collect();
let result = pool.execute(&query).await?;
match result {
ExecuteResult::Read {

@ -3,6 +3,7 @@ use super::{
StatefulDrawableComponent,
};
use crate::components::command::CommandInfo;
use crate::components::utils::input::Input;
use crate::config::KeyConfig;
use crate::event::Key;
use anyhow::Result;
@ -20,9 +21,7 @@ use unicode_width::UnicodeWidthStr;
pub struct TableFilterComponent {
key_config: KeyConfig,
pub table: Option<Table>,
input: Vec<char>,
input_idx: usize,
input_cursor_position: u16,
pub input: Input,
completion: CompletionComponent,
}
@ -31,30 +30,23 @@ impl TableFilterComponent {
Self {
key_config: key_config.clone(),
table: None,
input: Vec::new(),
input_idx: 0,
input_cursor_position: 0,
input: Input::new(),
completion: CompletionComponent::new(key_config, "", false),
}
}
pub fn input_str(&self) -> String {
self.input.iter().collect()
}
pub fn reset(&mut self) {
self.input.reset();
self.table = None;
self.input = Vec::new();
self.input_idx = 0;
self.input_cursor_position = 0;
}
fn update_completion(&mut self) {
let input = &self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i < &self.input_idx)
.filter(|(i, _)| i < &self.input.cursor_index)
.map(|(_, i)| i)
.collect::<String>()
.split(' ')
@ -69,16 +61,23 @@ impl TableFilterComponent {
let mut input = Vec::new();
let first = self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i < &self.input_idx.saturating_sub(self.completion.word().len()))
.filter(|(i, _)| {
i < &self
.input
.cursor_index
.saturating_sub(self.completion.word().len())
})
.map(|(_, c)| c.to_string())
.collect::<Vec<String>>();
let last = self
.input
.value
.iter()
.enumerate()
.filter(|(i, _)| i >= &self.input_idx)
.filter(|(i, _)| i >= &self.input.cursor_index)
.map(|(_, c)| c.to_string())
.collect::<Vec<String>>();
@ -102,21 +101,21 @@ impl TableFilterComponent {
input.extend(middle.clone());
input.extend(last);
self.input = input.join("").chars().collect();
self.input_idx += &middle.len();
self.input.value = input.join("").chars().collect();
self.input.cursor_index += &middle.len();
if is_last_word {
self.input_idx += 1;
self.input.cursor_index += 1;
}
self.input_idx -= self.completion.word().len();
self.input_cursor_position += middle
self.input.cursor_index -= self.completion.word().len();
self.input.cursor_position += middle
.join("")
.chars()
.map(compute_character_width)
.sum::<u16>();
if is_last_word {
self.input_cursor_position += " ".to_string().width() as u16
self.input.cursor_position += " ".to_string().width() as u16
}
self.input_cursor_position -= self
self.input.cursor_position -= self
.completion
.word()
.chars()
@ -140,8 +139,8 @@ impl StatefulDrawableComponent for TableFilterComponent {
),
Span::from(format!(
" {}",
if focused || !self.input.is_empty() {
self.input.iter().collect::<String>()
if focused || !self.input.value.is_empty() {
self.input.value.iter().collect::<String>()
} else {
"Enter a SQL expression in WHERE clause to filter records".to_string()
}
@ -167,7 +166,7 @@ impl StatefulDrawableComponent for TableFilterComponent {
format!("{} ", table.name.to_string())
})
.width() as u16)
.saturating_add(self.input_cursor_position),
.saturating_add(self.input.cursor_position),
0,
)?;
};
@ -181,7 +180,7 @@ impl StatefulDrawableComponent for TableFilterComponent {
.map_or(String::new(), |table| table.name.to_string())
.width()
+ 1) as u16)
.saturating_add(self.input_cursor_position)
.saturating_add(self.input.cursor_position)
.min(area.right().saturating_sub(2)),
area.y + 1,
)
@ -194,8 +193,6 @@ impl Component for TableFilterComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect();
// apply comletion candidates
if key == self.key_config.enter {
return self.complete();
@ -203,58 +200,23 @@ impl Component for TableFilterComponent {
self.completion.selected_candidate();
match key {
Key::Char(c) => {
self.input.insert(self.input_idx, c);
self.input_idx += 1;
self.input_cursor_position += compute_character_width(c);
self.update_completion();
Ok(EventState::Consumed)
}
Key::Delete | Key::Backspace => {
if input_str.width() > 0 && !self.input.is_empty() && self.input_idx > 0 {
let last_c = self.input.remove(self.input_idx - 1);
self.input_idx -= 1;
self.input_cursor_position -= compute_character_width(last_c);
self.completion.update("");
}
Ok(EventState::Consumed)
}
Key::Left => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx -= 1;
self.input_cursor_position = self
.input_cursor_position
.saturating_sub(compute_character_width(self.input[self.input_idx]));
self.completion.update("");
match self.input.handle_key(key) {
(Some(matched_key), input_updated) => match matched_key {
Key::Char(_) => {
self.update_completion();
return Ok(EventState::Consumed);
}
Ok(EventState::Consumed)
}
Key::Ctrl('a') => {
if !self.input.is_empty() && self.input_idx > 0 {
self.input_idx = 0;
self.input_cursor_position = 0
}
Ok(EventState::Consumed)
}
Key::Right => {
if self.input_idx < self.input.len() {
let next_c = self.input[self.input_idx];
self.input_idx += 1;
self.input_cursor_position += compute_character_width(next_c);
self.completion.update("");
Key::Ctrl(_) => {
return Ok(EventState::Consumed);
}
Ok(EventState::Consumed)
}
Key::Ctrl('e') => {
if self.input_idx < self.input.len() {
self.input_idx = self.input.len();
self.input_cursor_position = self.input_str().width() as u16;
_ => {
if input_updated {
self.completion.update("");
}
return Ok(EventState::Consumed);
}
Ok(EventState::Consumed)
}
key => self.completion.event(key),
},
_ => self.completion.event(key),
}
}
}
@ -266,12 +228,12 @@ mod test {
#[test]
fn test_complete() {
let mut filter = TableFilterComponent::new(KeyConfig::default());
filter.input_idx = 2;
filter.input = vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g'];
filter.input.cursor_index = 2;
filter.input.value = vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g'];
filter.completion.update("an");
assert!(filter.complete().is_ok());
assert_eq!(
filter.input,
filter.input.value,
vec!['A', 'N', 'D', ' ', 'c', 'd', 'e', 'f', 'g']
);
}
@ -279,12 +241,12 @@ mod test {
#[test]
fn test_complete_end() {
let mut filter = TableFilterComponent::new(KeyConfig::default());
filter.input_idx = 9;
filter.input = vec!['a', 'b', ' ', 'c', 'd', 'e', 'f', ' ', 'i'];
filter.input.cursor_index = 9;
filter.input.value = vec!['a', 'b', ' ', 'c', 'd', 'e', 'f', ' ', 'i'];
filter.completion.update('i');
assert!(filter.complete().is_ok());
assert_eq!(
filter.input,
filter.input.value,
vec!['a', 'b', ' ', 'c', 'd', 'e', 'f', ' ', 'I', 'N', ' ']
);
}
@ -292,10 +254,13 @@ mod test {
#[test]
fn test_complete_no_candidates() {
let mut filter = TableFilterComponent::new(KeyConfig::default());
filter.input_idx = 2;
filter.input = vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g'];
filter.input.cursor_index = 2;
filter.input.value = vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g'];
filter.completion.update("foo");
assert!(filter.complete().is_ok());
assert_eq!(filter.input, vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g']);
assert_eq!(
filter.input.value,
vec!['a', 'n', ' ', 'c', 'd', 'e', 'f', 'g']
);
}
}

@ -0,0 +1,401 @@
use super::{is_nonalphanumeric, is_whitespace};
use crate::components::compute_character_width;
use crate::event::Key;
use std::ops::Range;
use unicode_width::UnicodeWidthStr;
pub struct Input {
pub value: Vec<char>,
pub cursor_position: u16,
pub cursor_index: usize,
}
impl Input {
pub fn new() -> Self {
Self {
value: Vec::new(),
cursor_index: 0,
cursor_position: 0,
}
}
pub fn value_str(&self) -> String {
self.value.iter().collect()
}
pub fn value_width(&self) -> u16 {
self.value_str().width() as u16
}
fn width_for(&self, chars: &[char]) -> u16 {
chars.iter().collect::<String>().width() as u16
}
pub fn reset(&mut self) {
self.value = Vec::new();
self.cursor_index = 0;
self.cursor_position = 0;
}
fn cannot_move_left(&self) -> bool {
self.value.is_empty() || self.cursor_index == 0 || self.value_width() == 0
}
fn cannot_move_right(&self) -> bool {
self.cursor_index == self.value.len()
}
fn find_index_for_char_of_kind(
&self,
range: Range<usize>,
is_char_of_kind: &dyn Fn(char) -> bool,
) -> Option<usize> {
let mut result = None;
for i in range {
if is_char_of_kind(self.value[i]) {
result = Some(i);
break;
}
}
return result;
}
fn cursor_index_backwards_until(&self, is_char_of_kind: &dyn Fn(char) -> bool) -> usize {
let range = 0..self.cursor_index - 1;
match self.find_index_for_char_of_kind(range, is_char_of_kind) {
Some(index) => index + 1,
None => 0,
}
}
fn cursor_index_forwards_until(&self, is_char_of_kind: &dyn Fn(char) -> bool) -> usize {
let range = self.cursor_index + 1..self.value.len();
match self.find_index_for_char_of_kind(range, is_char_of_kind) {
Some(index) => index,
None => self.value.len(),
}
}
fn delete_left_until(&mut self, new_cursor_index: usize) {
let mut tail = self.value.to_vec().drain(self.cursor_index..).collect();
self.cursor_index = new_cursor_index;
self.value.truncate(new_cursor_index);
self.cursor_position = self.value_width();
self.value.append(&mut tail);
}
fn delete_right_until(&mut self, index: usize) {
let mut tail = self.value.to_vec().drain(index..).collect();
self.value.truncate(self.cursor_index);
self.value.append(&mut tail);
}
pub fn handle_key(&mut self, key: Key) -> (Option<Key>, bool) {
match key {
Key::Char(c) => {
self.value.insert(self.cursor_index, c);
self.cursor_index += 1;
self.cursor_position += compute_character_width(c);
return (Some(key), true);
}
Key::Delete | Key::Backspace => {
if self.cannot_move_left() {
return (Some(key), false);
}
let last_c = self.value.remove(self.cursor_index - 1);
self.cursor_index -= 1;
self.cursor_position -= compute_character_width(last_c);
return (Some(key), true);
}
Key::Right | Key::Ctrl('f') => {
if self.cannot_move_right() {
return (Some(key), false);
}
let next_c = self.value[self.cursor_index];
self.cursor_index += 1;
self.cursor_position += compute_character_width(next_c);
return (Some(key), true);
}
Key::Ctrl('e') => {
if self.cannot_move_right() {
return (Some(key), false);
}
self.cursor_index = self.value.len();
self.cursor_position = self.value_width();
return (Some(key), true);
}
Key::Alt('f') => {
if self.cannot_move_right() {
return (Some(key), false);
}
let new_cursor_index = self.cursor_index_forwards_until(&is_nonalphanumeric);
self.cursor_index = new_cursor_index;
self.cursor_position = self.width_for(&self.value[0..new_cursor_index]);
return (Some(key), true);
}
Key::Alt('d') => {
if self.cannot_move_right() {
return (Some(key), false);
}
let index = self.cursor_index_forwards_until(&is_nonalphanumeric);
self.delete_right_until(index);
return (Some(key), true);
}
Key::Ctrl('d') => {
if self.cannot_move_right() {
return (Some(key), false);
}
self.delete_right_until(self.cursor_index + 1);
return (Some(key), true);
}
Key::Left | Key::Ctrl('b') => {
if self.cannot_move_left() {
return (Some(key), false);
}
self.cursor_index -= 1;
self.cursor_position = self
.cursor_position
.saturating_sub(compute_character_width(self.value[self.cursor_index]));
return (Some(key), true);
}
Key::Ctrl('a') => {
if self.cannot_move_left() {
return (Some(key), false);
}
self.cursor_index = 0;
self.cursor_position = 0;
return (Some(key), true);
}
Key::Alt('b') => {
if self.cannot_move_left() {
return (Some(key), false);
}
let new_cursor_index = self.cursor_index_backwards_until(&is_nonalphanumeric);
self.cursor_index = new_cursor_index;
self.cursor_position = self.width_for(&self.value[0..new_cursor_index]);
return (Some(key), true);
}
Key::Ctrl('w') => {
if self.cannot_move_left() {
return (Some(key), false);
}
let new_cursor_index = self.cursor_index_backwards_until(&is_whitespace);
self.delete_left_until(new_cursor_index);
return (Some(key), true);
}
Key::AltBackspace => {
if self.cannot_move_left() {
return (Some(key), false);
}
let new_cursor_index = self.cursor_index_backwards_until(&is_nonalphanumeric);
self.delete_left_until(new_cursor_index);
return (Some(key), true);
}
_ => (None, false),
}
}
}
#[cfg(test)]
mod test {
use super::Input;
use crate::components::compute_character_width;
use crate::event::Key;
#[test]
fn test_adds_new_chars_for_char_key() {
let mut input = Input::new();
input.handle_key(Key::Char('a'));
assert_eq!(input.value, vec!['a']);
assert_eq!(input.cursor_index, 1);
assert_eq!(input.cursor_position, compute_character_width('a'));
}
#[test]
fn test_deletes_chars_for_backspace_and_delete_key() {
let mut input = Input::new();
input.value = vec!['a', 'b'];
input.cursor_index = 2;
input.cursor_position = input.value_width();
input.handle_key(Key::Delete);
input.handle_key(Key::Backspace);
assert_eq!(input.value, Vec::<char>::new());
assert_eq!(input.cursor_index, 0);
assert_eq!(input.cursor_position, 0);
}
#[test]
fn test_moves_cursor_left_for_left_key() {
let mut input = Input::new();
input.value = vec!['a'];
input.cursor_index = 1;
input.cursor_position = compute_character_width('a');
let (matched_key, input_changed) = input.handle_key(Key::Left);
assert_eq!(matched_key, Some(Key::Left));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a']);
assert_eq!(input.cursor_index, 0);
assert_eq!(input.cursor_position, 0);
}
#[test]
fn test_moves_cursor_right_for_right_key() {
let mut input = Input::new();
input.value = vec!['a'];
let (matched_key, input_changed) = input.handle_key(Key::Right);
assert_eq!(matched_key, Some(Key::Right));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a']);
assert_eq!(input.cursor_index, 1);
assert_eq!(input.cursor_position, compute_character_width('a'));
}
#[test]
fn test_jumps_to_beginning_for_ctrl_a() {
let mut input = Input::new();
input.value = vec!['a', 'b', 'c'];
input.cursor_index = 3;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Ctrl('a'));
assert_eq!(matched_key, Some(Key::Ctrl('a')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', 'b', 'c']);
assert_eq!(input.cursor_index, 0);
assert_eq!(input.cursor_position, 0);
}
#[test]
fn test_jumps_to_end_for_ctrl_e() {
let mut input = Input::new();
input.value = vec!['a', 'b', 'c'];
input.cursor_index = 0;
input.cursor_position = 0;
let (matched_key, input_changed) = input.handle_key(Key::Ctrl('e'));
assert_eq!(matched_key, Some(Key::Ctrl('e')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', 'b', 'c']);
assert_eq!(input.cursor_index, 3);
assert_eq!(input.cursor_position, input.value_width());
}
#[test]
fn test_deletes_word_for_ctrl_w() {
let mut input = Input::new();
input.value = vec!['a', ' ', 'c', 'd'];
input.cursor_index = 3;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Ctrl('w'));
assert_eq!(matched_key, Some(Key::Ctrl('w')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', ' ', 'd']);
assert_eq!(input.cursor_index, 2);
}
#[test]
fn test_deletes_backwards_til_nonalphanumeric_for_alt_backspace() {
let mut input = Input::new();
input.value = vec!['a', '-', 'c', 'd'];
input.cursor_index = 3;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::AltBackspace);
assert_eq!(matched_key, Some(Key::AltBackspace));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', '-', 'd']);
assert_eq!(input.cursor_index, 2);
}
#[test]
fn test_deletes_forwards_til_nonalphanumeric_for_alt_d() {
let mut input = Input::new();
input.value = vec!['a', 'b', '-', 'd'];
input.cursor_index = 1;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Alt('d'));
assert_eq!(matched_key, Some(Key::Alt('d')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', '-', 'd']);
assert_eq!(input.cursor_index, 1);
}
#[test]
fn test_deletes_char_under_current_cursor_for_ctrl_d() {
let mut input = Input::new();
input.value = vec!['a', 'b', 'c', 'd'];
input.cursor_index = 1;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Ctrl('d'));
assert_eq!(matched_key, Some(Key::Ctrl('d')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', 'c', 'd']);
assert_eq!(input.cursor_index, 1);
}
#[test]
fn test_moves_backwards_til_nonalphanumeric_for_alt_b() {
let mut input = Input::new();
input.value = vec!['a', '-', 'c', 'd'];
input.cursor_index = 3;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Alt('b'));
assert_eq!(matched_key, Some(Key::Alt('b')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', '-', 'c', 'd']);
assert_eq!(input.cursor_index, 2);
}
#[test]
fn test_moves_forwards_til_nonalphanumeric_for_alt_f() {
let mut input = Input::new();
input.value = vec!['a', 'b', '-', 'c'];
input.cursor_index = 1;
input.cursor_position = input.value_width();
let (matched_key, input_changed) = input.handle_key(Key::Alt('f'));
assert_eq!(matched_key, Some(Key::Alt('f')));
assert_eq!(input_changed, true);
assert_eq!(input.value, vec!['a', 'b', '-', 'c']);
assert_eq!(input.cursor_index, 2);
}
}

@ -1 +1,10 @@
pub mod input;
pub mod scroll_vertical;
pub fn is_whitespace(c: char) -> bool {
c.is_whitespace()
}
pub fn is_nonalphanumeric(c: char) -> bool {
!c.is_alphanumeric()
}

@ -69,6 +69,7 @@ pub enum Key {
Char(char),
Ctrl(char),
Alt(char),
AltBackspace,
Unknown,
}
@ -135,6 +136,10 @@ impl From<event::KeyEvent> for Key {
code: event::KeyCode::Esc,
..
} => Key::Esc,
event::KeyEvent {
code: event::KeyCode::Backspace,
modifiers: event::KeyModifiers::ALT,
} => Key::AltBackspace,
event::KeyEvent {
code: event::KeyCode::Backspace,
..

Loading…
Cancel
Save