Skip to content

Commit

Permalink
refactor: use new text input widget
Browse files Browse the repository at this point in the history
- add a new text input widget
- command input uses text input component
- autocompletion and state of content of input lives in
  widgets::text_input::TextInputState
- history logic still lives in old command input field
  • Loading branch information
robertpsoane committed Jul 7, 2024
1 parent 20d6f41 commit ce8651a
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 98 deletions.
28 changes: 0 additions & 28 deletions src/autocomplete.rs

This file was deleted.

96 changes: 31 additions & 65 deletions src/components/input_field.rs → src/components/command_input.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
use color_eyre::eyre::{Context, Result};
use itertools::min;
use ratatui::{
layout::{Margin, Rect},
prelude::*,
style::Style,
text::{Line, Span},
widgets::{Block, Padding, Paragraph},
Frame,
};
use ratatui::{layout::Rect, Frame};
use std::{collections::VecDeque, fmt::Debug};

use tokio::sync::mpsc::Sender;

use crate::{
autocomplete::Autocomplete,
context::AppContext,
events::transition::send_transition,
events::{message::MessageResponse, Key, Message, Transition},
events::{message::MessageResponse, transition::send_transition, Key, Message, Transition},
traits::Component,
widgets::text_input::{Autocomplete, TextInput, TextInputState},
};

use super::text_input_wrapper::TextInputWrapper;

const QUIT: &str = "quit";
const Q: &str = "q";
const IMAGE: &str = "image";
Expand All @@ -28,76 +22,70 @@ const CONTAINER: &str = "container";
const CONTAINERS: &str = "containers";

#[derive(Debug)]
pub struct InputField {
input: String,
prompt: String,
pub struct CommandInput {
tx: Sender<Message<Key, Transition>>,
candidate: Option<String>,
ac: Autocomplete<'static>,
history: History,
text_input: TextInputWrapper,
}

impl InputField {
impl CommandInput {
pub fn new(tx: Sender<Message<Key, Transition>>, prompt: String) -> Self {
let ac: Autocomplete =
Autocomplete::from(vec![QUIT, Q, IMAGE, IMAGES, CONTAINER, CONTAINERS]);
Self {
input: String::new(),
prompt,
tx,
candidate: None,
ac: Autocomplete::from(vec![QUIT, Q, IMAGE, IMAGES, CONTAINER, CONTAINERS]),
history: History::new(),
text_input: TextInputWrapper::new(prompt, Some(ac)),
}
}

pub fn initialise(&mut self) {
self.input = String::new();
self.candidate = None;
self.text_input.reset();
self.history.reset_idx();
}

pub async fn update(&mut self, message: Key) -> Result<MessageResponse> {
// NB - for now the CommandInput is over-riding bits of the TextInputWrapper
// This should probably be fixed by a generic TextInput widget over a
// trait which defines the interaction between the widget and its state struct
// for now unlikely we'll need history for anything else, so autocomplete
// as an optional first class citizen is fine & we'll see what happens
//
// Similarly, it could be that autocomplete varies, or we want different types
// of autocomplete, which would trigger a refactor?
match message {
Key::Char(c) => {
Key::Char(_) => {
self.history.reset_idx();
self.input.push(c);
let input = &self.input;
self.candidate = self.ac.get_completion(input);
}
Key::Tab => {
if let Some(candidate) = &self.candidate {
self.input.clone_from(candidate)
}
self.text_input.update(message)?;
}
Key::Up => {
self.history.conditional_set_working_buffer(&self.input);
let input_value = self.text_input.get_value();
self.history.conditional_set_working_buffer(&input_value);
if let Some(v) = &self.history.next() {
self.input.clone_from(v);
self.text_input.set_input(v.clone());
}
}
Key::Down => {
if let Some(v) = &self.history.previous() {
self.input.clone_from(v);
self.text_input.set_input(v.clone());
}
}
Key::Backspace => {
self.input.pop();
}
Key::Enter => {
self.history.add_value(&self.input);
self.history.add_value(&self.text_input.get_value());
self.history.reset_idx();
self.submit()
.await
.context("unable to submit user command")?;
}

_ => return Ok(MessageResponse::NotConsumed),
_ => return self.text_input.update(message),
}

Ok(MessageResponse::Consumed)
}

async fn submit(&mut self) -> Result<()> {
let transition = match &*self.input {
let transition = match &*self.text_input.get_value() {
Q | QUIT => Some(Transition::Quit),
IMAGE | IMAGES => Some(Transition::ToImagePage(AppContext::default())),
CONTAINER | CONTAINERS => Some(Transition::ToContainerPage(AppContext::default())),
Expand All @@ -122,31 +110,9 @@ impl InputField {
}
}

impl Component for InputField {
impl Component for CommandInput {
fn draw(&mut self, f: &mut Frame<'_>, area: Rect) {
let block = Block::bordered()
.border_type(ratatui::widgets::BorderType::Plain)
.padding(Padding::left(300));

f.render_widget(block, area);

let inner_body_margin = Margin::new(2, 1);
let body_inner = area.inner(inner_body_margin);

let mut input_text = vec![
Span::styled::<String, Style>(format!("{} ", self.prompt), Style::new().green()),
Span::raw(self.input.clone()),
];

if let Some(candidate) = &self.candidate {
if let Some(delta) = candidate.strip_prefix(&self.input as &str) {
input_text
.push(Span::raw(delta).style(Style::default().add_modifier(Modifier::DIM)))
}
}

let p = Paragraph::new(Line::from(input_text));
f.render_widget(p, body_inner)
self.text_input.draw(f, area);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod alert_modal;
pub mod boolean_modal;
pub mod command_input;
pub mod footer;
pub mod header;
pub mod help;
pub mod input_field;
pub mod resize_notice;
pub mod text_input_wrapper;
53 changes: 53 additions & 0 deletions src/components/text_input_wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use color_eyre::eyre::Result;

use crate::{
events::{message::MessageResponse, Key},
traits::Component,
widgets::text_input::{Autocomplete, TextInput, TextInputState},
};

/// Component which TextInput widget providing baked in keyboard input handling
#[derive(Debug)]
pub struct TextInputWrapper {
prompt: String,
state: TextInputState,
}

impl TextInputWrapper {
pub fn new(prompt: String, autocomplete: Option<Autocomplete<'static>>) -> Self {
let state = TextInputState::new(autocomplete);
Self { prompt, state }
}

pub fn reset(&mut self) {
self.state.reset()
}

pub fn update(&mut self, message: Key) -> Result<MessageResponse> {
match message {
Key::Char(c) => self.state.push_character(c),
Key::Tab => self.state.accept_autocomplete_candidate(),
Key::Backspace => {
self.state.pop_character();
}
_ => return Ok(MessageResponse::NotConsumed),
}

Ok(MessageResponse::Consumed)
}

pub fn get_value(&mut self) -> String {
self.state.get_value().clone()
}

pub fn set_input(&mut self, value: String) {
self.state.set_input(value);
}
}

impl Component for TextInputWrapper {
fn draw(&mut self, f: &mut ratatui::Frame<'_>, area: ratatui::prelude::Rect) {
let text_input = TextInput::new(Some(self.prompt.clone()));
f.render_stateful_widget(text_input, area, &mut self.state);
}
}
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod autocomplete;
pub mod callbacks;
pub mod components;
pub mod config;
Expand Down
6 changes: 3 additions & 3 deletions src/ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use tokio::sync::mpsc::Sender;
use crate::{
components::{
alert_modal::{AlertModal, ModalState},
command_input::CommandInput,
footer::Footer,
header::Header,
input_field::InputField,
resize_notice::ResizeScreen,
},
config::Config,
Expand All @@ -38,7 +38,7 @@ pub struct App {
title: Header,
page_manager: PageManager,
footer: Footer,
input_field: InputField,
input_field: CommandInput,
modal: Option<AlertModal<ModalType>>,
}

Expand All @@ -65,7 +65,7 @@ impl App {
title: Header::new(config.clone()),
page_manager: body,
footer: Footer::new(config.clone()),
input_field: InputField::new(tx, config.prompt),
input_field: CommandInput::new(tx, config.prompt),
modal: None,
};
Ok(app)
Expand Down
1 change: 1 addition & 0 deletions src/widgets/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod modal;
pub mod text_input;
Loading

0 comments on commit ce8651a

Please sign in to comment.