Primitive text entry
This commit is contained in:
parent
4d4d6a3470
commit
8c0a033f3c
9 changed files with 66 additions and 31 deletions
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "zte2"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ fn main() -> Result<(), Error> {
|
|||
|
||||
// Have the UI handle events
|
||||
if ui
|
||||
.handle(Event::from_raw(ev))
|
||||
.handle(&mut state, Event::from_raw(ev))
|
||||
.map_or(false, |r| r.should_end())
|
||||
{
|
||||
return Ok(());
|
||||
|
|
|
|||
21
src/state.rs
21
src/state.rs
|
|
@ -1,10 +1,9 @@
|
|||
use crate::{
|
||||
theme,
|
||||
Action, Args, Color, Error, Event, theme,
|
||||
ui::{self, Element as _, Resp},
|
||||
Action, Args, Color, Error, Event,
|
||||
};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||
use slotmap::{new_key_type, HopSlotMap};
|
||||
use slotmap::{HopSlotMap, new_key_type};
|
||||
use std::{io, path::PathBuf};
|
||||
|
||||
new_key_type! {
|
||||
|
|
@ -12,8 +11,10 @@ new_key_type! {
|
|||
pub struct CursorId;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cursor {}
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct Cursor {
|
||||
pub pos: usize,
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
pub path: PathBuf,
|
||||
|
|
@ -25,6 +26,7 @@ impl Buffer {
|
|||
pub fn new(path: PathBuf) -> Result<Self, Error> {
|
||||
let chars = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => s.chars().collect(),
|
||||
// If the file doesn't exist, create a new file
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Vec::new(),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
|
@ -34,6 +36,15 @@ impl Buffer {
|
|||
cursors: HopSlotMap::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, pos: usize, c: char) {
|
||||
self.chars.insert(pos, c);
|
||||
self.cursors.values_mut().for_each(|c| {
|
||||
if c.pos >= pos {
|
||||
c.pos += 1
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::{theme, Error};
|
||||
use crate::{Error, theme};
|
||||
|
||||
pub use crossterm::{
|
||||
cursor::SetCursorStyle as CursorStyle, event::Event as TerminalEvent, style::Color,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
cursor, event, style, terminal, ExecutableCommand, QueueableCommand, SynchronizedUpdate,
|
||||
ExecutableCommand, QueueableCommand, SynchronizedUpdate, cursor, event, style, terminal,
|
||||
};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ impl Input {
|
|||
}
|
||||
|
||||
impl Element for Input {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp, Event> {
|
||||
match event.to_action(|e| {
|
||||
e.to_char()
|
||||
.map(Action::Char)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ pub use self::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
terminal::{Color, Rect},
|
||||
Action, Dir, Event, State,
|
||||
terminal::{Color, Rect},
|
||||
};
|
||||
|
||||
pub enum CannotEnd {}
|
||||
|
|
@ -59,7 +59,7 @@ pub trait Element<CanEnd = CannotEnd> {
|
|||
///
|
||||
/// If handled, convert into a series of secondary actions.
|
||||
/// If unhandled, return the original event to be handled by a lower element.
|
||||
fn handle(&mut self, event: Event) -> Result<Resp<CanEnd>, Event>;
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event>;
|
||||
}
|
||||
|
||||
pub trait Visual {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use super::*;
|
||||
use crate::state::{BufferId, Cursor, CursorId};
|
||||
use crate::{
|
||||
state::{BufferId, Cursor, CursorId},
|
||||
terminal::CursorStyle,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Doc {
|
||||
|
|
@ -17,13 +20,24 @@ impl Doc {
|
|||
}
|
||||
|
||||
impl Element for Doc {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp, Event> {
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return Err(event);
|
||||
};
|
||||
let Some(cursor) = buffer.cursors.get(self.cursor) else {
|
||||
return Err(event);
|
||||
};
|
||||
|
||||
match event.to_action(|e| {
|
||||
e.to_char()
|
||||
.map(Action::Char)
|
||||
.or_else(|| e.to_move().map(Action::Move))
|
||||
.or_else(|| e.to_pane_move().map(Action::PaneMove))
|
||||
}) {
|
||||
Some(Action::Char(c)) => {
|
||||
buffer.insert(cursor.pos, c);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
_ => Err(event),
|
||||
}
|
||||
}
|
||||
|
|
@ -31,12 +45,22 @@ impl Element for Doc {
|
|||
|
||||
impl Visual for Doc {
|
||||
fn render(&self, state: &State, frame: &mut Rect) {
|
||||
if let Some(buffer) = state.buffers.get(self.buffer) {
|
||||
for (i, line) in buffer.chars.split(|c| *c == '\n').enumerate() {
|
||||
frame.text([0, i], line);
|
||||
let Some(buffer) = state.buffers.get(self.buffer) else {
|
||||
return;
|
||||
};
|
||||
let Some(cursor) = buffer.cursors.get(self.cursor) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut n = 0;
|
||||
for (i, line) in buffer.chars.split(|c| *c == '\n').enumerate() {
|
||||
frame.text([0, i], line);
|
||||
|
||||
if (n..=n + line.len()).contains(&cursor.pos) {
|
||||
frame.set_cursor([cursor.pos - n, i], CursorStyle::BlinkingBar);
|
||||
}
|
||||
} else {
|
||||
frame.text([0, 0], "[Error: no buffer]".chars());
|
||||
|
||||
n += line.len() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +88,7 @@ impl Panes {
|
|||
}
|
||||
|
||||
impl Element for Panes {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp, Event> {
|
||||
match event.to_action(|e| e.to_pane_move().map(Action::PaneMove)) {
|
||||
Some(Action::PaneMove(Dir::Left)) => {
|
||||
self.selected = (self.selected + self.panes.len() - 1) % self.panes.len();
|
||||
|
|
@ -79,7 +103,7 @@ impl Element for Panes {
|
|||
if let Some(pane) = self.panes.get_mut(self.selected) {
|
||||
// Pass to pane
|
||||
match pane {
|
||||
Pane::Doc(doc) => doc.handle(event),
|
||||
Pane::Doc(doc) => doc.handle(state, event),
|
||||
}
|
||||
} else {
|
||||
// No active pane, don't handle
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ impl Prompt {
|
|||
}
|
||||
|
||||
impl Element<CanEnd> for Prompt {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
match event.to_action(|e| {
|
||||
if e.is_cancel() {
|
||||
Some(Action::Cancel)
|
||||
|
|
@ -64,7 +64,7 @@ impl Element<CanEnd> for Prompt {
|
|||
))))
|
||||
}
|
||||
}
|
||||
_ => self.input.handle(event).map(Resp::into_can_end),
|
||||
_ => self.input.handle(state, event).map(Resp::into_can_end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ pub struct Show {
|
|||
}
|
||||
|
||||
impl Element<CanEnd> for Show {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
match event.to_action(|e| {
|
||||
if e.is_cancel() {
|
||||
Some(Action::Cancel)
|
||||
|
|
@ -113,7 +113,7 @@ pub struct Confirm {
|
|||
}
|
||||
|
||||
impl Element<CanEnd> for Confirm {
|
||||
fn handle(&mut self, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
match event.to_action(|e| {
|
||||
if e.is_cancel() || e.to_char() == Some('n') {
|
||||
Some(Action::Cancel)
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ impl Root {
|
|||
}
|
||||
|
||||
impl Element<CanEnd> for Root {
|
||||
fn handle(&mut self, mut event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
fn handle(&mut self, state: &mut State, mut event: Event) -> Result<Resp<CanEnd>, Event> {
|
||||
// Pass the event down through the list of tasks until we meet one that can handle it
|
||||
let mut task_idx = self.tasks.len();
|
||||
let action = loop {
|
||||
task_idx = match task_idx.checked_sub(1) {
|
||||
Some(task_idx) => task_idx,
|
||||
None => {
|
||||
break match self.panes.handle(event) {
|
||||
break match self.panes.handle(state, event) {
|
||||
Ok(resp) => resp.action,
|
||||
Err(event) => event.to_action(|e| {
|
||||
if e.is_prompt() {
|
||||
|
|
@ -42,14 +42,14 @@ impl Element<CanEnd> for Root {
|
|||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let res = match &mut self.tasks[task_idx] {
|
||||
Task::Prompt(p) => p.handle(event),
|
||||
Task::Show(s) => s.handle(event),
|
||||
Task::Confirm(c) => c.handle(event),
|
||||
Task::Prompt(p) => p.handle(state, event),
|
||||
Task::Show(s) => s.handle(state, event),
|
||||
Task::Confirm(c) => c.handle(state, event),
|
||||
};
|
||||
|
||||
match res {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue