From 6cbf446d7b1c91450533323071937eb594ce49af Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Wed, 24 Aug 2022 21:10:21 +0200 Subject: edit: implement command mode input handling --- src/edit.rs | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 18 deletions(-) diff --git a/src/edit.rs b/src/edit.rs index e40bfd7..97c1abf 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -1,10 +1,11 @@ use vtcol::{Palette, Rgb, Scheme}; +use std::fmt; use std::rc::Rc; use anyhow::{anyhow, Result}; -use slint::{Color, VecModel}; +use slint::{re_exports::KeyEvent, Color, VecModel}; slint::slint! { import { HorizontalBox, VerticalBox } from "std-widgets.slint"; @@ -17,6 +18,7 @@ slint::slint! { callback format-rgb-component(int) -> string; callback color-of-rgb-component(string, int) -> color; callback component-of-rgb(string, color) -> int; + callback handle-command-buffer(KeyEvent, string, int) -> string; property mode: "normal"; /* normal | insert | command */ } @@ -132,10 +134,26 @@ slint::slint! { height : t.preferred-height + 3pt; background : @linear-gradient(90deg, #002b36 0%, #073642 100%); + property cursor : 0; property text <=> t.text; + callback input (KeyEvent) -> bool; + callback clear; - t:= TextInput { - text: ":"; + clear () => { t.text = ""; } + + input (k) => { + t.text = Aux.handle-command-buffer (k, t.text, cursor); + + if (t.text == "") { + /* User backspaced her way to the left. */ + return true; + } + + return false; + } + + t := Text { + text: ""; color: #fdf6e3; vertical-alignment: center; } @@ -161,12 +179,11 @@ slint::slint! { /* Escape always gets you back to normal mode. */ if (event.text == Keys.Escape) { debug("enter normal"); - master.enter-normal-mode(); + master.enter-normal-mode (); } else if (Aux.mode == "command") { - if (event.text == Keys.Return) { - // TODO: execute typed command - master.enter-normal-mode(); + if (command.input (event)) { + master.enter-normal-mode (); } } else if (Aux.mode == "insert") { @@ -258,8 +275,16 @@ slint::slint! { debug ("selected row above/below, now", Aux.selected); } - enter-command-mode () => { Aux.mode = "command"; } - enter-normal-mode () => { Aux.mode = "normal" ; } + enter-command-mode () => { + Aux.mode = "command"; + command.text = ":"; + } + + enter-normal-mode () => { + Aux.mode = "normal" ; + command.clear (); + } + enter-insert-mode () => { Aux.mode = "insert" ; } status := HorizontalBox { @@ -298,21 +323,76 @@ slint::slint! { } } -pub struct Edit -{ - name: Option, +#[derive(Debug)] +enum KeyInput { + Printable(String), + Escape, + Return, + Backspace, + Delete, + Tab, + Left, + Right, + Up, + Down, + /** Not representable. */ + Junk, +} + +impl fmt::Display for KeyInput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let k = match self { + Self::Printable(s) => return write!(f, "‘{}’", s), + Self::Escape => "ESC", + Self::Return => "\\r", + Self::Backspace => "\\b", + Self::Delete => "DEL", + Self::Tab => "\\t", + Self::Left => "←", + Self::Right => "→", + Self::Up => "↑", + Self::Down => "↓", + Self::Junk => return write!(f, "{}", "Ø"), + }; + + write!(f, "key:{}", k) + } +} + +/** Crude filter for rejecting strings containing control chars. */ +fn is_printable(s: &str) -> bool { + s.chars().find(|c| c.is_control()).is_none() +} + +impl From<&KeyEvent> for KeyInput { + fn from(ev: &KeyEvent) -> Self { + match ev.text.as_str() { + "\u{0008}" => KeyInput::Backspace, + "\t" | "\u{000b}" => KeyInput::Tab, + "\n" | "\r" => KeyInput::Return, + "\u{000e}" => KeyInput::Left, + "\u{000f}" => KeyInput::Right, + "\u{0010}" => KeyInput::Up, + "\u{0011}" => KeyInput::Down, + "\u{001b}" => KeyInput::Escape, + "\u{007F}" => KeyInput::Delete, + any if is_printable(any) => KeyInput::Printable(any.into()), + _ => KeyInput::Junk, + } + } +} + +pub struct Edit { + name: Option, scheme: Scheme, } -impl Edit -{ - pub fn new(name: Option, scheme: Scheme) -> Self - { +impl Edit { + pub fn new(name: Option, scheme: Scheme) -> Self { Self { name, scheme } } - pub fn run(self) -> Result<()> - { + pub fn run(self) -> Result<()> { let Self { name, scheme } = self; let pal = Palette::try_from(&scheme)?.iter().collect::>(); @@ -387,6 +467,34 @@ impl Edit } }); + gui.global::().on_handle_command_buffer(|ev, text, _pos| { + let text = match KeyInput::from(&ev) { + KeyInput::Printable(s) => { + let mut text = text.to_string(); + text.push_str(&s); + text + }, + KeyInput::Return => { + todo!(); + /* The empty string signals to leave command mode. */ + String::new() + }, + KeyInput::Backspace => match text.char_indices().next_back() { + Some((i, _)) => text[..i].into(), + None => text.to_string(), + }, + other => { + eprintln!( + "»»» command mode input: “{}”", + other.to_string() + ); + text.to_string() + }, + }; + eprintln!("»»» command buffer: “{}”", text); + text.into() + }); + if let Some(name) = name { gui.set_scheme_name(name.into()); } -- cgit v1.2.3