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, &database,
&table, &table,
0, 0,
if self.record_table.filter.input_str().is_empty() { if self.record_table.filter.input.value_str().is_empty() {
None None
} else { } else {
Some(self.record_table.filter.input_str()) Some(self.record_table.filter.input.value_str())
}, },
) )
.await?; .await?;
@ -224,7 +224,7 @@ impl App {
return Ok(EventState::Consumed); 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() { if let Some((database, table)) = self.databases.tree().selected_table() {
self.record_table.reset(); self.record_table.reset();
let (headers, records) = self let (headers, records) = self
@ -279,10 +279,11 @@ impl App {
&database, &database,
&table, &table,
index as u16, index as u16,
if self.record_table.filter.input_str().is_empty() { if self.record_table.filter.input.value_str().is_empty()
{
None None
} else { } else {
Some(self.record_table.filter.input_str()) Some(self.record_table.filter.input.value_str())
}, },
) )
.await?; .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::command::CommandInfo;
use crate::components::utils::input::Input;
use crate::event::Key; use crate::event::Key;
use anyhow::Result; use anyhow::Result;
use database_tree::Table; use database_tree::Table;
@ -11,34 +12,23 @@ use tui::{
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
Frame, Frame,
}; };
use unicode_width::UnicodeWidthStr;
pub struct DatabaseFilterComponent { pub struct DatabaseFilterComponent {
pub table: Option<Table>, pub table: Option<Table>,
input: Vec<char>, pub input: Input,
input_idx: usize,
input_cursor_position: u16,
} }
impl DatabaseFilterComponent { impl DatabaseFilterComponent {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
table: None, table: None,
input: Vec::new(), input: Input::new(),
input_idx: 0,
input_cursor_position: 0,
} }
} }
pub fn input_str(&self) -> String {
self.input.iter().collect()
}
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.table = None; self.table = None;
self.input = Vec::new(); self.input.reset();
self.input_idx = 0;
self.input_cursor_position = 0;
} }
} }
@ -46,10 +36,10 @@ impl DrawableComponent for DatabaseFilterComponent {
fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> { fn draw<B: Backend>(&self, f: &mut Frame<B>, area: Rect, focused: bool) -> Result<()> {
let query = Paragraph::new(Spans::from(format!( let query = Paragraph::new(Spans::from(format!(
"{:w$}", "{:w$}",
if self.input.is_empty() && !focused { if self.input.value.is_empty() && !focused {
"Filter tables".to_string() "Filter tables".to_string()
} else { } else {
self.input_str() self.input.value_str()
}, },
w = area.width as usize w = area.width as usize
))) )))
@ -63,7 +53,7 @@ impl DrawableComponent for DatabaseFilterComponent {
if focused { if focused {
f.set_cursor( 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, area.y,
) )
} }
@ -75,58 +65,9 @@ impl Component for DatabaseFilterComponent {
fn commands(&self, _out: &mut Vec<CommandInfo>) {} fn commands(&self, _out: &mut Vec<CommandInfo>) {}
fn event(&mut self, key: Key) -> Result<EventState> { fn event(&mut self, key: Key) -> Result<EventState> {
let input_str: String = self.input.iter().collect(); match self.input.handle_key(key) {
(Some(_), _) => Ok(EventState::Consumed),
match key { _ => Ok(EventState::NotConsumed),
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);
}
_ => (),
} }
Ok(EventState::NotConsumed)
} }
} }

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

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

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

Loading…
Cancel
Save