Added rendering, basic keybinds

This commit is contained in:
Ceferino Patino 2025-01-08 21:13:24 -06:00
commit 2183c97c58
Signed by: c4patino
SSH key fingerprint: SHA256:9fQ9TsujGrdNNi76mnsu63v7dS5JOmHRZEqBOl49OR8
8 changed files with 513 additions and 189 deletions

View file

@ -7,4 +7,8 @@ license = "MIT"
[dependencies]
clap = { version = "4.5.23", features = ["derive"] }
color-eyre = "0.6.3"
crossterm = "0.28.1"
tokio = { version = "1.42.0", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

236
src/editor/editor.rs Normal file
View file

@ -0,0 +1,236 @@
use color_eyre::Report;
use crossterm::{
cursor,
event::{self, poll, read, Event, KeyCode, KeyEvent, KeyModifiers},
execute, queue, style,
terminal::{self, ClearType},
};
use std::{
fs::File,
io::{self, BufRead, BufReader, Write},
sync::{atomic::AtomicBool, mpsc, Arc},
time::{Duration, Instant},
};
use tokio::{runtime::Runtime, sync::oneshot};
use super::Keymap;
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum Mode {
NORMAL,
COMMAND,
INSERT,
VISUAL,
}
#[derive(Debug)]
pub enum ControlCode {
CONTINUE,
QUIT,
}
#[derive(Debug)]
pub struct Editor {
buffer: Vec<String>,
dirty: bool,
stop: bool,
mode: Mode,
size: (u16, u16),
cursor: (u16, u16),
offset: (u16, u16),
keymap: Keymap,
last_key_time: Instant,
out: io::Stdout,
}
impl Drop for Editor {
fn drop(&mut self) {
let _ = execute!(self.out, style::ResetColor, terminal::LeaveAlternateScreen);
}
}
impl Editor {
pub fn new() -> Self {
let mut keymap = Keymap::new();
keymap.add_keybind(
vec![Mode::NORMAL],
vec![
KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL),
KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL),
],
|editor| Ok(editor.stop = true),
);
keymap.add_keybind(
vec![Mode::NORMAL],
vec![
KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CONTROL),
KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL),
KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL),
],
|editor| Ok(editor.buffer.push("Double QUIT".to_string())),
);
let mut editor = Self {
buffer: vec![String::new()],
dirty: true,
stop: false,
mode: Mode::NORMAL,
size: terminal::size().unwrap(),
cursor: (0, 0),
offset: (0, 0),
keymap,
last_key_time: Instant::now(),
out: io::stdout(),
};
let _ = execute!(editor.out, terminal::EnterAlternateScreen);
editor
}
pub fn load_file(&mut self, filename: &str) {
let file = match File::open(filename) {
Ok(file) => file,
Err(e) => {
eprintln!("Error opening file '{}': {}", filename, e);
return;
}
};
let reader = BufReader::new(file);
self.buffer = match reader.lines().collect::<Result<Vec<String>, _>>() {
Ok(lines) => lines,
Err(e) => {
eprintln!("Error reading lines from file '{}': {}", filename, e);
return;
}
};
}
pub fn run(&mut self) -> Result<(), Report> {
let (tx, mut rx) = mpsc::channel::<KeyEvent>();
let rt = Runtime::new()?;
rt.block_on(async {
tokio::spawn(async move {
Editor::key_event_listener(tx).await;
});
while !self.stop {
self.handle_key_event(&mut rx).await?;
self.render().await?;
}
self.buffer.push("This is outside main loop".to_string());
self.dirty = true;
self.render().await?;
Ok(())
})
}
async fn handle_key_event(&mut self, rx: &mut mpsc::Receiver<KeyEvent>) -> Result<(), Report> {
if self.last_key_time.elapsed().as_millis() > 1000 && !self.keymap.is_empty() {
self.buffer.push("TIMEOUT".to_string());
self.execute_keymap_action()?;
self.dirty = true;
}
let event = match rx.try_recv() {
Ok(event) => event,
Err(_) => return Ok(()),
};
let mut unresolved = self.keymap.traverse(&self.mode, event)?;
if unresolved.is_some() {
self.execute_keymap_action()?;
unresolved = self.keymap.traverse(&self.mode, event)?;
}
if let Some(unresolved) = unresolved {
self.buffer.push(format!(
"{} {:?} {:?}",
unresolved.code, unresolved.modifiers, self.last_key_time
));
}
if self.keymap.is_leaf() {
self.execute_keymap_action()?;
}
self.last_key_time = Instant::now();
self.dirty = true;
Ok(())
}
fn execute_keymap_action(&mut self) -> Result<(), Report> {
if let Some(action) = self.keymap.get_action() {
action.borrow_mut()(self)?;
};
self.keymap.clear();
Ok(())
}
async fn render(&mut self) -> Result<(), Report> {
if !self.dirty {
return Ok(());
}
queue!(
self.out,
style::ResetColor,
terminal::Clear(ClearType::All),
cursor::MoveTo(0, 0)
)?;
let max_lines = self.size.1 as usize;
let render = &self.buffer[self.offset.1 as usize..]
.iter()
.take(max_lines)
.collect::<Vec<_>>();
for line in render {
queue!(self.out, style::Print(line), cursor::MoveToNextLine(1))?;
}
let rendered_lines = render.len();
if rendered_lines < max_lines {
let empty_lines = max_lines - rendered_lines;
for _ in 0..empty_lines {
queue!(self.out, style::Print("~"), cursor::MoveToNextLine(1))?;
}
}
queue!(self.out, cursor::MoveTo(self.cursor.0, self.cursor.1))?;
self.out.flush()?;
self.dirty = false;
Ok(())
}
async fn key_event_listener(tx: mpsc::Sender<KeyEvent>) {
loop {
if !poll(Duration::from_millis(10)).unwrap() {
tokio::time::sleep(Duration::from_millis(10)).await;
continue;
}
if let Event::Key(key_event) = read().unwrap() {
if tx.send(key_event).is_err() {
break;
}
}
}
}
}

140
src/editor/keymap.rs Normal file
View file

@ -0,0 +1,140 @@
use color_eyre::Report;
use crossterm::event::KeyEvent;
use std::{cell::RefCell, collections::HashMap, fmt, io, rc::Rc};
use crate::editor::{ControlCode, Editor, Mode};
type ActionFn = dyn FnMut(&mut Editor) -> Result<(), Report>;
#[derive(Clone)]
struct KeyNode {
children: HashMap<KeyEvent, Rc<RefCell<KeyNode>>>,
action: Option<Rc<RefCell<ActionFn>>>,
}
impl fmt::Debug for KeyNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "KeyNode {{ children: {:?} }}", self.children)
}
}
impl KeyNode {
pub fn new() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
children: HashMap::new(),
action: None,
}))
}
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
}
pub fn insert(&mut self, sequence: Vec<KeyEvent>, action: Rc<RefCell<ActionFn>>) {
if sequence.is_empty() {
self.action = Some(action);
return;
}
let (key, sequence) = sequence.split_first().unwrap();
let next_node = self.children.entry(key.clone()).or_insert_with(KeyNode::new);
next_node.borrow_mut().insert(sequence.to_vec(), action)
}
}
#[derive(Debug)]
pub struct Keymap {
root: HashMap<Mode, Rc<RefCell<KeyNode>>>,
current: Option<Rc<RefCell<KeyNode>>>,
}
impl Keymap {
pub fn new() -> Self {
Self {
root: HashMap::new(),
current: None,
}
}
pub fn add_keybind<F>(&mut self, modes: Vec<Mode>, sequence: Vec<KeyEvent>, action: F)
where
F: FnMut(&mut Editor) -> Result<(), Report> + 'static,
{
let action = Rc::new(RefCell::new(action));
for mode in modes {
let mut root_node = self.root.entry(mode).or_insert_with(KeyNode::new).borrow_mut();
root_node.insert(sequence.clone(), action.clone());
}
}
pub fn traverse(&mut self, mode: &Mode, event: KeyEvent) -> Result<Option<KeyEvent>, Report> {
let current_node = match self.current {
Some(ref node) => node.clone(),
None => self.root.get(mode).unwrap().clone(),
};
let next_node = match current_node.borrow().children.get(&event) {
Some(node) => node.clone(),
None => return Ok(Some(event)),
};
self.current = Some(next_node);
Ok(None)
}
pub fn is_leaf(&self) -> bool {
match self.current {
Some(ref node) => node.borrow().is_leaf(),
None => false,
}
}
pub fn get_action(&self) -> Option<Rc<RefCell<ActionFn>>> {
if self.current.is_none() {
return None;
}
self.current.as_ref()?.borrow().action.clone()
}
pub fn clear(&mut self) {
self.current = None;
}
pub fn is_empty(&self) -> bool {
self.current.is_none()
}
}
//impl<W: io::Write> Keymap<W> {
// pub fn new() -> Self {
// Self {
// root: KeyNode::new(),
// command: Vec::new(),
// }
// }
//
// pub fn add_keybind<F>(&mut self, modes: Vec<Mode>, sequence: Vec<KeyEvent>, action: F)
// where
// F: FnMut(&mut Editor<W>) -> io::Result<ControlCode> + 'static,
// {
// for mode in modes {
// self.root.insert(mode, sequence.clone(), action);
// }
// }
//
// pub fn clear(&mut self) {
// self.command.clear();
// }
//
// pub fn traverse(&mut self, mode: Mode, event: KeyEvent) -> Option<&mut KeyNode<W>> {
// let mut current_node = &mut self.root;
//
// if let Some(next_node) = current_node.children.get_mut(&(mode, event)) {
// self.current_path.push((mode, event));
// self.current
// }
// }
//}

7
src/editor/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod editor;
mod keymap;
pub(crate) use self::editor::{ControlCode, Mode};
pub(crate) use self::keymap::Keymap;
pub use self::editor::Editor;

View file

@ -1,146 +1,35 @@
mod rope;
mod editor;
use std::{cmp, io};
use crossterm::{
cursor,
event::{read, Event, KeyCode, KeyEvent, KeyModifiers},
execute, queue, style,
terminal::{self, ClearType},
};
use std::time::Instant;
use clap::Parser;
use color_eyre::Report;
use crossterm::terminal;
enum Mode {
NORMAL,
INSERT,
VISUAL,
use editor::Editor;
use tracing_subscriber::EnvFilter;
struct RawModeGuard;
impl Drop for RawModeGuard {
fn drop(&mut self) {
let _ = terminal::disable_raw_mode();
}
}
#[allow(dead_code)]
struct Editor<W: io::Write> {
buffer: Vec<String>,
command: String,
out: W,
cursor: (u16, u16),
size: (u16, u16),
mode: Mode,
}
impl<W: io::Write> Editor<W> {
fn new(out: W) -> Self {
Self {
size: terminal::size().unwrap(),
buffer: Vec::new(),
command: String::new(),
out,
cursor: (0, 0),
mode: Mode::NORMAL,
fn setup() -> Result<(), Report> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
std::env::set_var("RUST_LIB_BACKTRACE", "1")
}
color_eyre::install()?;
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info")
}
fn setup(&mut self) -> io::Result<()> {
execute!(self.out, terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?;
self.buffer.push("".to_string());
for _ in 1..self.size.0 {
self.buffer.push("~".to_string());
}
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
Ok(())
}
fn teardown(&mut self) -> io::Result<()> {
execute!(self.out, style::ResetColor, terminal::LeaveAlternateScreen)?;
terminal::disable_raw_mode()
}
fn run(&mut self) -> io::Result<()> {
loop {
queue!(
self.out,
style::ResetColor,
terminal::Clear(ClearType::CurrentLine),
cursor::MoveTo(0, 0)
)?;
for message in &self.buffer {
queue!(self.out, style::Print(message), cursor::MoveToNextLine(1))?;
}
queue!(self.out, cursor::MoveTo(self.cursor.0, self.cursor.1))?;
self.out.flush()?;
match Self::read_key_event()? {
KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::CONTROL,
..
} => {
break;
}
KeyEvent {
code: KeyCode::Char('j'),
..
} => {
self.cursor = (self.cursor.0, cmp::min(self.cursor.1 + 1, self.size.1 - 1));
}
KeyEvent {
code: KeyCode::Char('k'),
..
} => {
self.cursor = (self.cursor.0, self.cursor.1.saturating_sub(1));
}
KeyEvent {
code: KeyCode::Char('h'),
..
} => {
self.cursor = (self.cursor.0.saturating_sub(1), self.cursor.1);
}
KeyEvent {
code: KeyCode::Char('l'),
..
} => {
self.cursor = (cmp::min(self.cursor.0 + 1, self.size.0 - 1), self.cursor.1);
}
KeyEvent {
code: KeyCode::Char('d'),
modifiers: KeyModifiers::CONTROL,
..
} => {
self.cursor = (
self.cursor.0,
cmp::min(self.cursor.1 + self.size.1 / 2, self.size.1 - 1),
);
}
KeyEvent {
code: KeyCode::Char('u'),
modifiers: KeyModifiers::CONTROL,
..
} => {
self.cursor = (
self.cursor.0,
cmp::max(self.cursor.1.saturating_sub(self.size.1 / 2), 0),
);
}
_ => {}
}
}
Ok(())
}
pub fn read_key_event() -> std::io::Result<KeyEvent> {
loop {
if let Ok(Event::Key(key_event)) = read() {
return Ok(key_event);
}
}
}
}
#[derive(Parser, Debug)]
@ -150,15 +39,22 @@ struct Args {
filename: Option<String>,
}
fn main() -> std::io::Result<()> {
fn main() -> Result<(), Report> {
setup()?;
let args = Args::parse();
println!("{:?}", args);
let mut editor = Editor::new(io::stdout());
{
terminal::enable_raw_mode()?;
let _raw_mode_guard = RawModeGuard;
//editor.setup()?;
//editor.run()?;
//editor.teardown()
let mut editor = Editor::new();
if let Some(filename) = &args.filename {
editor.load_file(filename);
};
editor.run()?;
}
Ok(())
}

View file

@ -1,51 +0,0 @@
use std::rc::Rc;
enum Node {
Leaf(String),
Internal {
left: Rc<Node>,
right: Rc<Node>,
weight: usize,
},
}
impl Node {
fn len(&self) -> usize {
match self {
Node::Leaf(s) => s.len(),
Node::Internal { weight, right, .. } => weight + right.len(),
}
}
fn new_leaf(text: &str) -> Rc<Self> {
Rc::new(Node::Leaf(text.to_string()))
}
fn new_internal(left: Rc<Self>, right: Rc<Self>) -> Rc<Self> {
Rc::new(Node::Internal {
weight: left.len(),
left,
right,
})
}
}
pub struct Rope {
root: Rc<Node>,
}
impl Rope {
pub fn new(text: &str) -> Self {
Self {
root: Node::new_leaf(text),
}
}
pub fn len(&self) -> usize {
self.root.len()
}
pub fn insert(&mut self, index: usize, text: &str) {
self.root = self.insert_rec(&self.root, index, text);
}
}

1
src/util/mod.rs Normal file
View file

@ -0,0 +1 @@
pub(crate) mod rope;

91
src/util/rope.rs Normal file
View file

@ -0,0 +1,91 @@
use std::fmt;
use std::rc::Rc;
enum Node {
Leaf(String),
Internal {
left: Rc<Node>,
right: Rc<Node>,
weight: usize,
},
}
impl Node {
fn new_leaf(text: &str) -> Rc<Self> {
Rc::new(Node::Leaf(text.to_string()))
}
fn new_internal(left: Rc<Self>, right: Rc<Self>) -> Rc<Self> {
Rc::new(Node::Internal {
weight: left.len(),
left,
right,
})
}
fn len(&self) -> usize {
match self {
Node::Leaf(s) => s.len(),
Node::Internal { weight, right, .. } => weight + right.len(),
}
}
}
pub struct Rope {
root: Rc<Node>,
}
impl Rope {
pub fn new(text: &str) -> Self {
Self {
root: Node::new_leaf(text),
}
}
pub fn len(&self) -> usize {
self.root.len()
}
pub fn insert(&mut self, index: usize, text: &str) {
self.root = self.insert_rec(&self.root, index, text);
}
fn insert_rec(&self, node: &Rc<Node>, index: usize, text: &str) -> Rc<Node> {
match node.as_ref() {
Node::Leaf(existing) => {
let mut new_text = String::with_capacity(existing.len() + text.len());
new_text.push_str(&existing[..index]);
new_text.push_str(text);
new_text.push_str(&existing[index..]);
Node::new_leaf(&new_text)
}
Node::Internal { left, right, weight } => {
if index < *weight {
let left = self.insert_rec(left, index, text);
return Node::new_internal(left, right.clone());
} else {
let right = self.insert_rec(right, index - weight, text);
return Node::new_internal(left.clone(), right);
}
}
}
}
fn collect_text(&self, node: &Rc<Node>, result: &mut String) {
match node.as_ref() {
Node::Leaf(text) => result.push_str(text),
Node::Internal { left, right, .. } => {
self.collect_text(left, result);
self.collect_text(right, result);
}
}
}
}
impl fmt::Display for Rope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut result = String::new();
self.collect_text(&self.root, &mut result);
f.write_str(&result)
}
}