summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2024-08-01 20:13:55 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2024-08-01 20:13:55 +0200
commita807d4e6fb96c4d8b8585b7dbb862e53486562ec (patch)
tree92481d73423fd925f4dd034d68cbfd2e30e216d5
parent417cee4eeeaf7516dfeb59cdbe34fed18f30e0f7 (diff)
add evalsto
-rw-r--r--src/config/evalsto.rs64
-rw-r--r--src/config/mod.rs2
-rw-r--r--src/config/theme/colorpair.rs106
-rw-r--r--src/config/theme/mod.rs4
-rw-r--r--src/config/theme/sheetview.rs39
-rw-r--r--src/config/theme/style.rs119
-rw-r--r--src/lua/mod.rs6
-rw-r--r--src/lua/ownedfunction.rs17
-rw-r--r--src/widgets/sheetview/mod.rs110
-rw-r--r--src/widgets/statusbar.rs85
10 files changed, 399 insertions, 153 deletions
diff --git a/src/config/evalsto.rs b/src/config/evalsto.rs
new file mode 100644
index 0000000..10b2dd1
--- /dev/null
+++ b/src/config/evalsto.rs
@@ -0,0 +1,64 @@
+use std::{marker::PhantomData, sync::Arc};
+
+use mlua::{FromLua, IntoLua, Lua, Result, Value};
+
+use crate::lua::ownedfunction::OwnedFunction;
+
+#[derive(Clone, Debug)]
+pub enum EvalTo<T, A> {
+ Function(Arc<OwnedFunction>, PhantomData<A>),
+ Value(T),
+}
+
+impl<T, A> EvalTo<T, A> {
+ pub fn get<'lua>(&'lua self, args: A, lua: &'lua Lua) -> Result<T>
+ where
+ T: FromLua<'lua> + Clone,
+ A: IntoLua<'lua>,
+ {
+ match self {
+ EvalTo::Function(value, _) => {
+ let func = value.get(lua);
+ T::from_lua(func.call(args)?, lua)
+ }
+ EvalTo::Value(value) => Ok(value.clone()),
+ }
+ }
+
+ pub const fn new(value: T) -> Self {
+ Self::Value(value)
+ }
+}
+
+impl<'lua, T, A> FromLua<'lua> for EvalTo<T, A>
+where
+ T: FromLua<'lua> + Clone,
+ A: IntoLua<'lua>,
+{
+ fn from_lua(
+ value: mlua::prelude::LuaValue<'lua>,
+ lua: &'lua mlua::prelude::Lua,
+ ) -> Result<Self> {
+ if value.is_function() {
+ Ok(Self::Function(
+ Arc::new(OwnedFunction::new(value, lua)),
+ PhantomData,
+ ))
+ } else {
+ Ok(Self::Value(T::from_lua(value, lua)?))
+ }
+ }
+}
+
+impl<'lua, T, A> IntoLua<'lua> for EvalTo<T, A>
+where
+ T: FromLua<'lua> + Clone + IntoLua<'lua>,
+ A: IntoLua<'lua>,
+{
+ fn into_lua(self, lua: &'lua mlua::prelude::Lua) -> Result<Value<'lua>> {
+ match self {
+ EvalTo::Function(value, _) => Ok(value.get(lua).into_lua(lua)?),
+ EvalTo::Value(value) => value.into_lua(lua),
+ }
+ }
+}
diff --git a/src/config/mod.rs b/src/config/mod.rs
index e6c4952..6e88306 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -6,7 +6,9 @@ use self::theme::Theme;
pub mod theme;
pub mod constants;
+pub mod evalsto;
+#[derive(Debug)]
pub struct GlobalConfig {
pub theme: Theme,
}
diff --git a/src/config/theme/colorpair.rs b/src/config/theme/colorpair.rs
deleted file mode 100644
index 77e6af4..0000000
--- a/src/config/theme/colorpair.rs
+++ /dev/null
@@ -1,106 +0,0 @@
-use std::str::FromStr;
-
-use mlua::{FromLua, IntoLua};
-use ratatui::style::{Color, Styled, Stylize};
-
-#[derive(Clone)]
-pub struct ColorPair {
- pub fg: Color,
- pub bg: Color,
-}
-
-impl ColorPair {
- pub const fn new(fg: Color, bg: Color) -> Self {
- ColorPair { bg, fg }
- }
-
- pub fn apply<S, I>(&self, s: S) -> <I as Styled>::Item
- where
- S: Styled<Item = I>,
- I: Styled,
- {
- s.bg(self.bg).fg(self.fg)
- }
-}
-
-impl<'lua> FromLua<'lua> for ColorPair {
- fn from_lua(
- value: mlua::prelude::LuaValue<'lua>,
- _lua: &'lua mlua::prelude::Lua,
- ) -> mlua::prelude::LuaResult<Self> {
- if value.is_table() {
- let table = value.as_table().unwrap();
- if let Ok(fg) = table.get::<_, LuaColor>(1) {
- if let Ok(bg) = table.get::<_, LuaColor>(2) {
- return Ok(ColorPair::new(fg.into(), bg.into()));
- }
- } else if let Ok(fg) = table.get::<_, LuaColor>("fg") {
- if let Ok(bg) = table.get::<_, LuaColor>("bg") {
- return Ok(ColorPair::new(fg.into(), bg.into()));
- }
- }
- }
-
- return Err(mlua::Error::runtime("could not parse colorpair"));
- }
-}
-
-impl<'lua> IntoLua<'lua> for ColorPair {
- fn into_lua(
- self,
- lua: &'lua mlua::prelude::Lua,
- ) -> mlua::prelude::LuaResult<mlua::prelude::LuaValue<'lua>> {
- let table = lua.create_table()?;
- table.set("fg", LuaColor::from(self.fg))?;
- table.set("bg", LuaColor::from(self.bg))?;
- Ok(table.into_lua(lua)?)
- }
-}
-
-struct LuaColor {
- color: Color,
-}
-
-impl LuaColor {
- fn new(color: Color) -> Self {
- Self { color }
- }
-}
-
-impl From<Color> for LuaColor {
- fn from(value: Color) -> Self {
- LuaColor::new(value)
- }
-}
-
-impl Into<Color> for LuaColor {
- fn into(self) -> Color {
- self.color
- }
-}
-
-impl<'lua> FromLua<'lua> for LuaColor {
- fn from_lua(
- value: mlua::prelude::LuaValue<'lua>,
- _lua: &'lua mlua::prelude::Lua,
- ) -> mlua::prelude::LuaResult<Self> {
- if value.is_string() {
- let str = value.as_str().unwrap();
- match Color::from_str(str) {
- Ok(color) => Ok(color.into()),
- Err(_) => Err(mlua::Error::runtime("color has wrong format")),
- }
- } else {
- Err(mlua::Error::runtime("color has wrong format"))
- }
- }
-}
-
-impl<'lua> IntoLua<'lua> for LuaColor {
- fn into_lua(
- self,
- lua: &'lua mlua::prelude::Lua,
- ) -> mlua::prelude::LuaResult<mlua::prelude::LuaValue<'lua>> {
- Ok(self.color.to_string().to_lowercase().into_lua(lua)?)
- }
-}
diff --git a/src/config/theme/mod.rs b/src/config/theme/mod.rs
index b7baf3a..35101c5 100644
--- a/src/config/theme/mod.rs
+++ b/src/config/theme/mod.rs
@@ -5,10 +5,10 @@ use self::sheetview::SheetViewTheme;
use super::DUMMY_CONFIG;
-pub mod colorpair;
+pub mod style;
pub mod sheetview;
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct Theme {
pub sheetview: SheetViewTheme,
}
diff --git a/src/config/theme/sheetview.rs b/src/config/theme/sheetview.rs
index de5751a..5590349 100644
--- a/src/config/theme/sheetview.rs
+++ b/src/config/theme/sheetview.rs
@@ -1,26 +1,23 @@
-use mlua::{IntoLua, Table, UserData};
+use mlua::{IntoLua, UserData};
use ratatui::style::Color;
-use crate::config::GlobalConfig;
+use crate::{config::{evalsto::EvalTo, GlobalConfig}, sheet::cell::CellRef};
-use super::colorpair::ColorPair;
+use super::style::Style;
-#[derive(Clone)]
+#[derive(Debug, Clone)]
pub struct SheetViewTheme {
- pub cursor: ColorPair,
- pub selection: ColorPair,
- pub cell: (ColorPair, ColorPair),
+ pub cursor: Style,
+ pub selection: Style,
+ pub cell: EvalTo<Style, CellRef>,
}
impl SheetViewTheme {
pub const fn new() -> Self {
Self {
- cursor: ColorPair::new(Color::Black, Color::White),
- selection: ColorPair::new(Color::White, Color::DarkGray),
- cell: (
- ColorPair::new(Color::White, Color::Black),
- ColorPair::new(Color::White, Color::DarkGray),
- ),
+ cursor: Style::new().fg(Color::Black).bg(Color::White),
+ selection: Style::new().fg(Color::White).bg(Color::DarkGray),
+ cell: EvalTo::Value(Style::new().fg(Color::White).bg(Color::Black)),
}
}
}
@@ -38,31 +35,27 @@ macro_rules! cfg_mut {
}
impl UserData for SheetViewTheme {
- fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: &mut F) {
+ fn add_fields<'lua, F: mlua::prelude::LuaUserDataFields<'lua, Self>>(fields: & mut F) {
fields.add_field_function_get("cursor", |_, _| Ok(cfg!().cursor.clone()));
- fields.add_field_function_set("cursor", |_, _, pair: ColorPair| {
+ fields.add_field_function_set("cursor", |_, _, pair: Style| {
cfg_mut!().cursor = pair;
Ok(())
});
fields.add_field_function_get("selection", |_, _| Ok(cfg!().selection.clone()));
- fields.add_field_function_set("selection", |_, _, pair: ColorPair| {
+ fields.add_field_function_set("selection", |_, _, pair: Style| {
cfg_mut!().selection = pair;
Ok(())
});
fields.add_field_function_get("cell", |lua, _| {
- let table = lua.create_table()?;
- let sel = cfg!().cell.clone();
- table.set(1, sel.0)?;
- table.set(2, sel.1)?;
- Ok(table.into_lua(lua)?)
+ Ok(cfg!().cell.clone().into_lua(lua)?)
});
- fields.add_field_function_set("cell", |_, _, pair: Table| {
- cfg_mut!().cell = (pair.get(1)?, pair.get(2)?);
+ fields.add_field_function_set("cell", |_, _, cell: EvalTo<Style, CellRef>| {
+ cfg_mut!().cell = cell;
Ok(())
});
}
diff --git a/src/config/theme/style.rs b/src/config/theme/style.rs
new file mode 100644
index 0000000..6e8d161
--- /dev/null
+++ b/src/config/theme/style.rs
@@ -0,0 +1,119 @@
+use std::str::FromStr;
+
+use mlua::{FromLua, IntoLua};
+use ratatui::style::{Color, Styled, Stylize};
+
+#[derive(Clone, Copy, Debug)]
+pub struct Style {
+ pub fg: Option<Color>,
+ pub bg: Option<Color>,
+ pub italic: bool,
+ pub bold: bool,
+ pub underlined: bool
+}
+
+impl Style {
+ pub const fn new() -> Self {
+ Self {
+ fg: None,
+ bg: None,
+ italic: false,
+ bold: false,
+ underlined: false,
+ }
+ }
+
+ pub const fn fg(mut self, color: Color) -> Self {
+ self.fg = Some(color);
+ self
+ }
+
+ pub const fn bg(mut self, color: Color) -> Self {
+ self.bg = Some(color);
+ self
+ }
+
+ pub fn apply<S, I>(&self, s: S) -> <S as Styled>::Item
+ where
+ S: Styled<Item = I>,
+ I: Styled,
+ {
+ let mut style = ratatui::style::Style::default();
+
+ if let Some(fg) = self.fg {
+ style = style.fg(fg)
+ }
+
+ if let Some(bg) = self.bg {
+ style = style.bg(bg)
+ }
+
+ if self.italic {
+ style = style.italic()
+ }
+
+ if self.bold {
+ style = style.bold()
+ }
+
+ if self.underlined {
+ style = style.underlined()
+ }
+
+ s.set_style(style)
+ }
+}
+
+impl<'lua> FromLua<'lua> for Style {
+ fn from_lua(
+ value: mlua::prelude::LuaValue<'lua>,
+ _lua: &'lua mlua::prelude::Lua,
+ ) -> mlua::prelude::LuaResult<Self> {
+ if value.is_table() {
+ let table = value.as_table().unwrap();
+ let mut style = Style::new();
+
+ if let Ok(fg) = table.get::<_, String>("fg").or_else(|_| table.get(1)) {
+ match Color::from_str(&fg) {
+ Ok(fg) => style.fg = Some(fg),
+ Err(_) => return Err(mlua::Error::runtime("could not parse fg"))
+ }
+ }
+
+ if let Ok(bg) = table.get::<_, String>("bg").or_else(|_| table.get(2)) {
+ match Color::from_str(&bg) {
+ Ok(bg) => style.bg = Some(bg),
+ Err(_) => return Err(mlua::Error::runtime("could not parse bg"))
+ }
+ }
+
+ if let Ok(italic) = table.get::<_, bool>("italic") {
+ style.italic = italic
+ }
+
+ if let Ok(bold) = table.get::<_, bool>("bold") {
+ style.bold = bold
+ }
+
+ if let Ok(underlined) = table.get::<_, bool>("underlined") {
+ style.underlined = underlined
+ }
+
+ return Ok(style)
+ }
+
+ return Err(mlua::Error::runtime("could not parse style"));
+ }
+}
+
+impl<'lua> IntoLua<'lua> for Style {
+ fn into_lua(
+ self,
+ lua: &'lua mlua::prelude::Lua,
+ ) -> mlua::prelude::LuaResult<mlua::prelude::LuaValue<'lua>> {
+ let table = lua.create_table()?;
+ table.set("fg", self.fg.map(|s| s.to_string()).into_lua(lua)?)?;
+ table.set("bg", self.bg.map(|s| s.to_string()).into_lua(lua)?)?;
+ Ok(table.into_lua(lua)?)
+ }
+}
diff --git a/src/lua/mod.rs b/src/lua/mod.rs
index 0ddcd7f..7388890 100644
--- a/src/lua/mod.rs
+++ b/src/lua/mod.rs
@@ -1,4 +1,5 @@
use std::{
+ borrow::Borrow,
path::Path,
sync::{Mutex, MutexGuard},
};
@@ -6,9 +7,10 @@ use std::{
use lazy_static::lazy_static;
use mlua::prelude::*;
-use crate::sheet::cell::CellRef;
+use crate::{config::GlobalConfig, sheet::cell::CellRef};
pub mod iobuffer;
+pub mod ownedfunction;
pub mod runtime;
lazy_static! {
@@ -37,6 +39,8 @@ fn print(_: &Lua, args: LuaMultiValue) -> LuaResult<()> {
if let Some(ud) = arg.as_userdata() {
if ud.is::<CellRef>() {
writer.write(ud.borrow::<CellRef>().unwrap().value().to_string());
+ } else if ud.is::<GlobalConfig>() {
+ writer.write(format!("{:#?}", GlobalConfig::instance().borrow()))
} else {
writer.write(format!("{:#?}", ud));
}
diff --git a/src/lua/ownedfunction.rs b/src/lua/ownedfunction.rs
new file mode 100644
index 0000000..a8d81cf
--- /dev/null
+++ b/src/lua/ownedfunction.rs
@@ -0,0 +1,17 @@
+use mlua::{Function, Lua, RegistryKey, Value};
+
+#[derive(Debug)]
+pub struct OwnedFunction {
+ key: RegistryKey,
+}
+
+impl OwnedFunction {
+ pub fn new<'lua>(value: Value<'lua>, lua: &'lua Lua) -> Self {
+ let key = lua.create_registry_value(value).unwrap();
+ Self { key }
+ }
+
+ pub fn get<'lua>(&self, lua: &'lua Lua) -> Function<'lua> {
+ lua.registry_value(&self.key).unwrap()
+ }
+}
diff --git a/src/widgets/sheetview/mod.rs b/src/widgets/sheetview/mod.rs
index 40adfc7..fd0919d 100644
--- a/src/widgets/sheetview/mod.rs
+++ b/src/widgets/sheetview/mod.rs
@@ -9,7 +9,8 @@ use ratatui::{
};
use crate::{
- config::GlobalConfig,
+ config::{theme::style, GlobalConfig},
+ lua,
sheet::{
eval::EvalFunction,
register::{Register, SheetId},
@@ -144,7 +145,7 @@ impl SheetView {
}
pub fn handle_key_event(&mut self, event: KeyEvent) {
- match self.mode {
+ match &self.mode {
SheetViewMode::Normal => match event.code {
KeyCode::Char('j') => self.move_cursor_by((1, 0)),
KeyCode::Char('k') => self.move_cursor_by((-1, 0)),
@@ -153,11 +154,15 @@ impl SheetView {
KeyCode::Char('v') => {
self.selection_anchor = Some(self.cursor.clone());
self.mode = SheetViewMode::Visual
- },
+ }
KeyCode::Char('s') => {
self.editor.set_text("function(cell)\n\treturn \"\"\nend");
self.mode = SheetViewMode::Script;
}
+ KeyCode::Char(':') => {
+ self.mode = SheetViewMode::Command;
+ self.bar.set_input_mode(true)
+ }
_ => {}
},
SheetViewMode::Insert => {}
@@ -169,16 +174,42 @@ impl SheetView {
KeyCode::Esc | KeyCode::Char('v') => {
self.selection_anchor = None;
self.mode = SheetViewMode::Normal;
- },
+ }
KeyCode::Char('s') => {
self.editor.set_text("function(cell)\n\treturn \"\"\nend");
self.mode = SheetViewMode::Script;
}
+ KeyCode::Char(':') => {
+ self.mode = SheetViewMode::Command;
+ self.bar.set_input_mode(true)
+ }
_ => {}
},
- SheetViewMode::Command => {}
+ SheetViewMode::Command => match event.code {
+ KeyCode::Enter => {
+ if let Err(error) = lua::get().load(self.bar.input().unwrap_or("")).exec() {
+ self.mode = SheetViewMode::CommandError(error.to_string())
+ } else {
+ if self.selection_anchor.is_some() {
+ self.mode = SheetViewMode::Visual;
+ } else {
+ self.mode = SheetViewMode::Normal;
+ }
+ }
+
+ self.bar.set_input_mode(false);
+ }
+ KeyCode::Esc => {
+ if self.selection_anchor.is_some() {
+ self.mode = SheetViewMode::Visual;
+ } else {
+ self.mode = SheetViewMode::Normal;
+ }
+ }
+ _ => self.bar.handle_keyevent(event),
+ },
SheetViewMode::CommandError(_) => match event.code {
- KeyCode::Esc => self.mode = SheetViewMode::Normal,
+ KeyCode::Esc | KeyCode::Enter => self.mode = SheetViewMode::Normal,
_ => {}
},
SheetViewMode::Script => match event.code {
@@ -226,7 +257,7 @@ impl SheetView {
self.join_process_handle_on_finished();
}
SheetViewMode::EditorError(_) => match event.code {
- KeyCode::Esc => self.mode = SheetViewMode::Script,
+ KeyCode::Esc | KeyCode::Enter => self.mode = SheetViewMode::Script,
_ => {}
},
}
@@ -255,11 +286,22 @@ impl SheetView {
}
}
- fn areas(&self, area: Rect) -> (Rect, Rect, Rect, Rect) {
+ fn areas(&self, area: Rect) -> (Rect, Rect, Rect, Rect, Rect) {
let mut sheet = area;
let mut editor = Rect::default();
let mut error = Rect::default();
let mut progress = Rect::default();
+ let mut command_error = Rect::default();
+
+ if let SheetViewMode::CommandError(message) = &self.mode {
+ let layout = Layout::vertical([
+ Constraint::Min(1),
+ Constraint::Length(message.lines().count().min(15) as u16 + 1),
+ ])
+ .split(sheet);
+ sheet = layout[0];
+ command_error = layout[1];
+ }
if self.is_editor_visible() {
let layout =
@@ -282,7 +324,7 @@ impl SheetView {
}
}
- (sheet, editor, error, progress)
+ (sheet, editor, error, progress, command_error)
}
}
@@ -293,7 +335,7 @@ impl Widget for &mut SheetView {
{
let theme = GlobalConfig::instance().theme.sheetview.clone();
let progress;
- let (sheet_area, editor_area, error_area, progress_area) = self.areas(area);
+ let (sheet_area, editor_area, error_area, progress_area, cmd_error_area) = self.areas(area);
let separator = Block::default().borders(Borders::RIGHT).border_style(
Style::default()
.bg(Color::Rgb(29, 32, 33))
@@ -331,17 +373,20 @@ impl Widget for &mut SheetView {
for column in 0..(viewport_columns + 1) as u16 {
let (cell_pos_y, cell_pos_x) = (row + self.scroll.0, column + self.scroll.1);
- if let Some(cell) = sheet.get_cell(cell_pos_y as usize, cell_pos_x as usize) {
- let cell = cell.to_string() + " ";
+ if let Some(cell_ref) = sheet.get_ref(cell_pos_y as usize, cell_pos_x as usize)
+ {
+ let cell = cell_ref.value().to_string() + " ";
let line = if (cell_pos_y, cell_pos_x) == self.cursor {
theme.cursor.apply(cell.to_line())
} else if self.is_in_selection(cell_pos_y, cell_pos_x) {
theme.selection.apply(cell.to_line())
- } else if (row + column) % 2 == 0 {
- theme.cell.0.apply(cell.to_line())
} else {
- theme.cell.1.apply(cell.to_line())
+ theme
+ .cell
+ .get(cell_ref, &lua::get())
+ .unwrap_or(style::Style::new())
+ .apply(cell.to_line())
};
let rect = Rect::new(
@@ -357,6 +402,22 @@ impl Widget for &mut SheetView {
}
}
+ match &self.mode {
+ SheetViewMode::Command => {
+ self.bar.set_left(" COMMAND ");
+ }
+
+ _ => {
+ if self.selection_anchor.is_some() {
+ self.bar.set_left(" VISUAL ");
+ } else {
+ self.bar.set_left(" NORMAL ");
+ self.bar.set_middle_alignment(Alignment::Center);
+ self.bar.set_middle("Sheet");
+ }
+ }
+ }
+
self.bar.render(sheet_area, buf);
if self.is_editor_visible() {
@@ -383,6 +444,25 @@ impl Widget for &mut SheetView {
.render(error_inner, buf);
}
+ if let SheetViewMode::CommandError(message) = &self.mode {
+ let lines = message.lines().collect::<Vec<_>>();
+
+ let block = Block::default()
+ .title_bottom(" Error ")
+ .title_style(Style::default().on_red())
+ .border_style(Style::default().black().on_black())
+ .borders(Borders::BOTTOM);
+ let error_inner = block.inner(cmd_error_area);
+ block.render(cmd_error_area, buf);
+
+ let text = Text::from_iter(lines.iter().map(|s| s.to_line().white()));
+
+ Paragraph::new(text)
+ .wrap(Wrap { trim: true })
+ .bg(Color::Rgb(70, 25, 25))
+ .render(error_inner, buf);
+ }
+
progress = sheet.progress();
}
diff --git a/src/widgets/statusbar.rs b/src/widgets/statusbar.rs
index 97b514d..60b58a6 100644
--- a/src/widgets/statusbar.rs
+++ b/src/widgets/statusbar.rs
@@ -1,4 +1,10 @@
-use ratatui::{layout::{Alignment, Rect}, style::Style, text::ToLine, widgets::Widget};
+use ratatui::{
+ crossterm::event::{KeyCode, KeyEvent},
+ layout::{Alignment, Rect},
+ style::{Style, Stylize},
+ text::{ToLine, ToSpan},
+ widgets::Widget,
+};
#[derive(Clone)]
pub struct StatusBar {
@@ -9,6 +15,8 @@ pub struct StatusBar {
middle_alignment: Alignment,
right: String,
right_style: Style,
+ input: Option<String>,
+ cursor: u16,
}
impl StatusBar {
@@ -21,6 +29,8 @@ impl StatusBar {
middle_alignment: Alignment::Center,
right: String::new(),
right_style: Style::default(),
+ input: None,
+ cursor: 0,
}
}
@@ -105,12 +115,49 @@ impl StatusBar {
self.middle_alignment = alignment;
}
+ pub fn set_input_mode(&mut self, input: bool) {
+ if input {
+ self.input = Some(String::new());
+ self.cursor = 0;
+ } else {
+ self.input = None;
+ }
+ }
+
+ pub fn input(&self) -> Option<&str> {
+ self.input.as_ref().map(|s| s.as_str())
+ }
+
pub fn area(&self, rect: Rect) -> Rect {
let mut inner = rect.clone();
inner.height -= 1;
inner
}
+ pub fn handle_keyevent(&mut self, event: KeyEvent) {
+ if let Some(input) = &mut self.input {
+ match event.code {
+ KeyCode::Char(c) => {
+ input.insert(self.cursor as usize, c);
+ self.cursor += 1;
+ }
+ KeyCode::Backspace => {
+ if self.cursor > 0 {
+ self.cursor -= 1;
+ input.remove(self.cursor as usize).to_string();
+ }
+ }
+ KeyCode::Left => {
+ self.cursor = (self.cursor as i32 - 1).max(0) as u16;
+ }
+ KeyCode::Right => {
+ self.cursor = (self.cursor + 1).min(input.len() as u16);
+ }
+ _ => {}
+ }
+ }
+ }
+
fn layout(&self, mut area: Rect) -> (Rect, Rect, Rect) {
area.y += area.height - 1;
area.height = 1;
@@ -142,11 +189,37 @@ impl Widget for &mut StatusBar {
.left_aligned()
.style(self.left_style)
.render(left, buf);
- self.middle
- .to_line()
- .alignment(self.middle_alignment)
- .style(self.middle_style)
- .render(middle, buf);
+
+ if let Some(input) = &self.input {
+ (":".to_owned() + input)
+ .to_line()
+ .alignment(Alignment::Left)
+ .style(self.middle_style)
+ .render(middle, buf);
+
+ input
+ .chars()
+ .nth(self.cursor as usize)
+ .unwrap_or(' ')
+ .to_span()
+ .reversed()
+ .render(
+ Rect::new(
+ middle.x + 1 + self.cursor,
+ middle.y,
+ middle.width,
+ middle.height,
+ ),
+ buf,
+ );
+ } else {
+ self.middle
+ .to_line()
+ .alignment(self.middle_alignment)
+ .style(self.middle_style)
+ .render(middle, buf);
+ }
+
self.right
.to_line()
.right_aligned()