Added buffer insertion and deletion

This commit is contained in:
Ceferino Patino 2025-01-11 01:13:51 -06:00
commit 6e1e8f919e
Signed by: c4patino
SSH key fingerprint: SHA256:9fQ9TsujGrdNNi76mnsu63v7dS5JOmHRZEqBOl49OR8
9 changed files with 378 additions and 135 deletions

View file

@ -1 +1,2 @@
max_width = 120
use_small_heuristics = "Max"

View file

@ -1,21 +1,16 @@
use color_eyre::Report;
use crossterm::{
cursor,
event::{poll, read, Event, KeyEvent},
execute, queue, style,
terminal::{self, ClearType},
};
use crossterm::event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers};
use std::{
fs::File,
io::{self, BufRead, BufReader, Write},
io::{BufRead, BufReader},
sync::mpsc,
time::{Duration, Instant},
};
use tokio::runtime::Runtime;
use super::{default_keybinds, Keymap};
use crate::util::{Display, Keymap};
#[derive(Debug, Eq, PartialEq, Hash)]
#[derive(Eq, PartialEq, Hash)]
pub enum Mode {
NORMAL,
COMMAND,
@ -23,54 +18,35 @@ pub enum Mode {
VISUAL,
}
#[derive(Debug)]
pub struct Editor {
pub(crate) buffer: Vec<String>,
pub(crate) command: String,
pub(crate) dirty: bool,
pub(crate) stop: bool,
pub(crate) mode: Mode,
pub(crate) size: (u16, u16),
pub(crate) cursor: (u16, u16),
pub(crate) offset: (u16, u16),
pub(crate) display: Display,
pub(crate) keymap: Keymap,
pub(crate) last_key_time: Instant,
pub(crate) 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();
default_keybinds(&mut keymap);
let mut editor = Self {
Self {
buffer: vec![String::new()],
command: String::new(),
dirty: true,
stop: false,
mode: Mode::NORMAL,
size: terminal::size().unwrap(),
cursor: (0, 0),
offset: (0, 0),
display: Display::new(),
keymap,
keymap: Keymap::new(),
last_key_time: Instant::now(),
out: io::stdout(),
};
let _ = execute!(editor.out, terminal::EnterAlternateScreen);
editor
}
}
pub fn load_file(&mut self, filename: &str) {
@ -100,23 +76,22 @@ impl Editor {
tokio::spawn(async move {
Editor::key_event_listener(tx).await;
});
});
while !self.stop {
self.handle_key_event(&mut rx).await?;
self.render().await?;
while !self.stop {
self.handle_key_event(&mut rx)?;
if self.dirty {
self.display.render(&self.buffer, &self.command, &self.mode)?;
self.dirty = false;
}
}
self.buffer.push("This is outside main loop".to_string());
self.dirty = true;
self.render().await?;
Ok(())
})
Ok(())
}
async fn handle_key_event(&mut self, rx: &mut mpsc::Receiver<KeyEvent>) -> Result<(), Report> {
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;
}
@ -132,22 +107,69 @@ impl Editor {
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()?;
}
if let Some(unresolved) = unresolved {
if !unresolved.modifiers.intersects(KeyModifiers::ALT | KeyModifiers::CONTROL) {
self.handle_unresolved_key_event(unresolved);
}
}
self.last_key_time = Instant::now();
self.dirty = true;
Ok(())
}
fn handle_unresolved_key_event(&mut self, unresolved: KeyEvent) {
match self.mode {
Mode::COMMAND => {
if let KeyCode::Char(c) = unresolved.code {
self.command.push(c);
} else if unresolved.code == KeyCode::Backspace {
self.command.pop();
}
}
Mode::INSERT => match unresolved.code {
KeyCode::Char(c) => {
let (x, y) = self.display.cursor.position;
self.buffer[y as usize].insert(x as usize, c);
self.display.move_cursor((1, 0), &self.buffer);
}
KeyCode::Enter => {
let (x, y) = self.display.cursor.position;
let remaining = self.buffer[y as usize].split_off(x as usize);
self.buffer.insert((y + 1) as usize, remaining);
self.display.move_cursor((-(x as i16), 1), &self.buffer)
}
KeyCode::Delete => {
let (x, y) = self.display.cursor.position;
if x < self.buffer[y as usize].len() as u16 {
self.buffer[y as usize].remove(x as usize);
} else if y + 1 < self.buffer.len() as u16 {
let next_line = self.buffer.remove((y + 1) as usize);
self.buffer[y as usize].push_str(&next_line);
}
}
KeyCode::Backspace => {
let (x, y) = self.display.cursor.position;
if x > 0 {
self.buffer[y as usize].remove((x - 1) as usize);
self.display.move_cursor((-1, 0), &self.buffer);
} else if y > 0 {
let prev_line_len = self.buffer[(y - 1) as usize].len() as u16;
let current_line = self.buffer.remove(y as usize);
self.buffer[(y - 1) as usize].push_str(&current_line);
self.display.move_cursor((prev_line_len as i16, -1), &self.buffer);
}
}
_ => {}
},
_ => {}
}
}
fn execute_keymap_action(&mut self) -> Result<(), Report> {
if let Some(action) = self.keymap.get_action() {
action.borrow_mut()(self)?;
@ -157,44 +179,6 @@ impl Editor {
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() {

View file

@ -1,13 +0,0 @@
use super::{Keymap, Mode};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub fn default_keybinds(keymap: &mut Keymap) {
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),
);
}

View file

@ -1,9 +1,5 @@
mod editor;
mod keybinds;
mod keymap;
pub(crate) use self::editor::{ControlCode, Mode};
pub(crate) use self::keybinds::default_keybinds;
pub(crate) use self::keymap::Keymap;
pub(crate) use self::editor::Mode;
pub use self::editor::Editor;

130
src/macros.rs Normal file
View file

@ -0,0 +1,130 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crate::editor::{Editor, Mode};
macro_rules! add_keybind {
($editor:expr, $modes:expr, $sequence:expr, $action:expr) => {
$editor.keymap.add_keybind(
add_keybind!(@parse_modes $modes),
add_keybind!(@parse_keys $sequence),
$action,
);
};
(@parse_keys $keyseq:expr) => {{
let mut keys = Vec::new();
let mut chars = $keyseq.chars().peekable();
while let Some(c) = chars.next() {
if c == '<' {
let mut key_name = String::new();
while let Some(&peek) = chars.peek() {
if peek == '>' {
chars.next();
break;
}
key_name.push(chars.next().unwrap());
}
let (modifiers, key) = if let Some(split_idx) = key_name.find('-') {
let (modifier, key) = key_name.split_at(split_idx);
(modifier, &key[1..].to_string())
} else {
("", &key_name)
};
let modifiers = match modifiers {
"C" => KeyModifiers::CONTROL,
"S" => KeyModifiers::SHIFT,
"A" => KeyModifiers::ALT,
_ => KeyModifiers::NONE,
};
let key_event = match key.as_str() {
"BS" => KeyEvent::new(KeyCode::Backspace, modifiers),
"Tab" => KeyEvent::new(KeyCode::Tab, modifiers),
"CR" | "Enter" | "Return" => KeyEvent::new(KeyCode::Enter, modifiers),
"Esc" => KeyEvent::new(KeyCode::Esc, modifiers),
"Space" => KeyEvent::new(KeyCode::Char(' '), modifiers),
"Up" => KeyEvent::new(KeyCode::Up, modifiers),
"Down" => KeyEvent::new(KeyCode::Down, modifiers),
"Left" => KeyEvent::new(KeyCode::Left, modifiers),
"Right" => KeyEvent::new(KeyCode::Right, modifiers),
"Insert" => KeyEvent::new(KeyCode::Insert, modifiers),
"Del" => KeyEvent::new(KeyCode::Delete, modifiers),
"Home" => KeyEvent::new(KeyCode::Home, modifiers),
"End" => KeyEvent::new(KeyCode::End, modifiers),
"PageUp" => KeyEvent::new(KeyCode::PageUp, modifiers),
"PageDown" => KeyEvent::new(KeyCode::PageDown, modifiers),
key if key.starts_with('F') && key[1..].parse::<u8>().is_ok() => {
let f_num = key[1..].parse::<u8>().unwrap();
KeyEvent::new(KeyCode::F(f_num), modifiers)
}
key if key.len() == 1 => {
KeyEvent::new(KeyCode::Char(key.chars().next().unwrap()), modifiers)
}
_ => panic!("Unknown key: {}", key),
};
keys.push(key_event);
} else {
keys.push(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE));
}
}
keys
}};
(@parse_modes $modes:expr) => {{
$modes
.chars()
.filter_map(|c| match c {
'n' => Some(Mode::NORMAL),
'v' => Some(Mode::VISUAL),
'c' => Some(Mode::COMMAND),
'i' => Some(Mode::INSERT),
_ => None,
})
.collect::<Vec<_>>()
}};
}
pub fn default_keybinds(editor: &mut Editor) {
add_keybind!(editor, "ni", "<C-w><C-q>", |e| Ok(e.stop = true));
add_keybind!(editor, "n", "k", |e| {
e.display.move_cursor((0, -1), &e.buffer);
Ok(())
});
add_keybind!(editor, "n", "j", |e| {
e.display.move_cursor((0, 1), &e.buffer);
Ok(())
});
add_keybind!(editor, "n", "h", |e| {
e.display.move_cursor((-1, 0), &e.buffer);
Ok(())
});
add_keybind!(editor, "n", "l", |e| {
e.display.move_cursor((1, 0), &e.buffer);
Ok(())
});
add_keybind!(editor, "n", "i", |e| {
e.mode = Mode::INSERT;
Ok(())
});
add_keybind!(editor, "n", ":", |e| {
e.mode = Mode::COMMAND;
Ok(())
});
add_keybind!(editor, "ic", "<Esc>", |e| {
e.mode = Mode::NORMAL;
e.command.clear();
Ok(())
});
}

View file

@ -1,13 +1,16 @@
mod editor;
mod util;
use std::time::Instant;
#[macro_use]
mod macros;
use clap::Parser;
use color_eyre::Report;
use crossterm::terminal;
use tracing_subscriber::EnvFilter;
use editor::Editor;
use tracing_subscriber::EnvFilter;
use macros::default_keybinds;
struct RawModeGuard;
impl Drop for RawModeGuard {
@ -25,9 +28,7 @@ fn setup() -> Result<(), Report> {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info")
}
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
tracing_subscriber::fmt::fmt().with_env_filter(EnvFilter::from_default_env()).init();
Ok(())
}
@ -49,6 +50,8 @@ fn main() -> Result<(), Report> {
let _raw_mode_guard = RawModeGuard;
let mut editor = Editor::new();
default_keybinds(&mut editor);
if let Some(filename) = &args.filename {
editor.load_file(filename);
};

151
src/util/display.rs Normal file
View file

@ -0,0 +1,151 @@
use color_eyre::Report;
use crossterm::{
cursor::{self, SetCursorStyle},
execute, queue,
style::{self, Stylize},
terminal::{self, ClearType},
};
use std::io::{self, Write};
use crate::editor::Mode;
pub struct Display {
size: (u16, u16),
offset: (u16, u16),
pub(crate) cursor: Cursor,
pub(crate) out: io::Stdout,
}
pub struct Cursor {
pub(crate) position: (u16, u16),
pub(crate) max_column: u16,
}
impl Cursor {
pub fn new() -> Self {
Self { position: (0, 0), max_column: 0 }
}
fn move_by(&mut self, delta: (i16, i16), buffer: &Vec<String>) {
let saturate = |pos: u16, delta: i16| {
if delta.is_negative() {
pos.saturating_sub(delta.abs() as u16)
} else {
pos.saturating_add(delta as u16)
}
};
let (mut x, mut y) = self.position;
let (dx, dy) = delta;
if dx != 0 {
x = saturate(x, dx);
self.max_column = x;
}
if dy != 0 {
y = saturate(y, dy);
}
self.position = (x, y);
self.validate_cursor(buffer);
}
fn move_x(&mut self, new_x: u16) {
self.position.0 = new_x;
}
fn move_y(&mut self, new_y: u16) {
self.position.1 = new_y;
}
fn validate_cursor(&mut self, buffer: &Vec<String>) {
let (_x, y) = self.position;
if y >= buffer.len() as u16 {
self.move_y(buffer.len().saturating_sub(1) as u16);
}
let line_len = buffer[self.position.1 as usize].len() as u16;
self.move_x(self.max_column.min(line_len));
}
}
impl Drop for Display {
fn drop(&mut self) {
let _ = execute!(self.out, style::ResetColor, terminal::LeaveAlternateScreen);
}
}
impl Display {
pub fn new() -> Self {
let mut display =
Self { size: terminal::size().unwrap(), offset: (0, 0), cursor: Cursor::new(), out: io::stdout() };
let _ = execute!(display.out, terminal::EnterAlternateScreen);
display
}
pub fn move_cursor(&mut self, delta: (i16, i16), buffer: &Vec<String>) {
self.cursor.move_by(delta, buffer);
}
pub fn render(&mut self, buffer: &Vec<String>, command: &String, mode: &Mode) -> Result<(), Report> {
queue!(self.out, style::ResetColor, terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?;
let mut max_lines = self.size.1 as usize;
if !command.is_empty() {
max_lines -= 1;
}
let max_columns = self.size.0 as usize;
let render = buffer[self.offset.1 as usize..]
.iter()
.take(max_lines)
.map(|line| {
if line.len() > self.offset.0 as usize {
let start = self.offset.0 as usize;
let end = (start + max_columns).min(line.len());
&line[start..end]
} else {
""
}
})
.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))?;
}
}
if !command.is_empty() {
queue!(
self.out,
style::SetAttribute(style::Attribute::Bold),
style::Print(command),
style::SetAttribute(style::Attribute::Reset),
cursor::MoveToNextLine(1)
)?;
}
match mode {
Mode::INSERT => queue!(self.out, cursor::SetCursorStyle::BlinkingBar)?,
_ => queue!(self.out, cursor::SetCursorStyle::DefaultUserShape)?,
}
queue!(self.out, cursor::MoveTo(self.cursor.position.0, self.cursor.position.1))?;
self.out.flush()?;
Ok(())
}
}

View file

@ -1,8 +1,8 @@
use color_eyre::Report;
use crossterm::event::KeyEvent;
use std::{cell::RefCell, collections::HashMap, fmt, io, rc::Rc};
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use crate::editor::{ControlCode, Editor, Mode};
use crate::editor::{Editor, Mode};
type ActionFn = dyn FnMut(&mut Editor) -> Result<(), Report>;
@ -12,18 +12,14 @@ struct 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)
}
pub struct Keymap {
root: HashMap<Mode, Rc<RefCell<KeyNode>>>,
current: Option<Rc<RefCell<KeyNode>>>,
}
impl KeyNode {
pub fn new() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
children: HashMap::new(),
action: None,
}))
Rc::new(RefCell::new(Self { children: HashMap::new(), action: None }))
}
pub fn is_leaf(&self) -> bool {
@ -43,18 +39,9 @@ impl KeyNode {
}
}
#[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,
}
Self { root: HashMap::new(), current: None }
}
pub fn add_keybind<F>(&mut self, modes: Vec<Mode>, sequence: Vec<KeyEvent>, action: F)

View file

@ -1 +1,5 @@
pub(crate) mod rope;
pub(crate) mod display;
pub(crate) mod keymap;
pub(crate) use self::display::Display;
pub(crate) use self::keymap::Keymap;