From 31a1cf961eb3edc7ef6c36d07bf82fc5ff39aa84 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 11 Apr 2023 23:55:56 -0400 Subject: [PATCH] Move Buffer to new module --- src/buffer.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 116 +++--------------------------------------- 2 files changed, 144 insertions(+), 110 deletions(-) create mode 100644 src/buffer.rs diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..37d6d30 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023 Marceline Cramer + * Copyright (c) 2023 Emma Tebibyte + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +use std::io::Write; + +use crossterm::{cursor, ExecutableCommand}; +use ropey::Rope; + +use crate::theme::StyleStore; +use crate::{Cursor, Direction}; + +#[derive(Clone, Debug)] +pub struct Buffer { + text: Rope, +} + +impl Buffer { + pub fn from_str(text: &str) -> Self { + Self { + text: Rope::from_str(text), + } + } + + pub fn draw( + &self, + styles: &mut StyleStore, + cols: u16, + rows: u16, + scroll: Cursor, + out: &mut (impl ExecutableCommand + Write), + ) -> crossterm::Result { + let linenr_style = styles.get_scope("ui.linenr"); + let linenr_width = self.text.len_lines().ilog10() + 1; + let gutter_width = linenr_width + 1; + let text_width = cols as usize - gutter_width as usize; + + out.execute(cursor::MoveTo(0, 0))?; + + for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) { + // only the last line is empty and should be skipped + if line.len_chars() == 0 { + break; + } + + let row = row as usize + scroll.line; + let linenr = format!("{:width$} ", row, width = linenr_width as usize); + linenr_style.print_styled(out, &linenr)?; + + let lhs = scroll.column; + let width = line.len_chars().saturating_sub(1); // lop off whitespace + if lhs < width { + let window = text_width.min(width - lhs); + let rhs = lhs + window; + write!(out, "{}", line.slice(lhs..rhs))?; + } + + out.execute(cursor::MoveToNextLine(1))?; + } + + Ok(gutter_width) + } + + pub fn remove(&mut self, cursor: Cursor) { + let index = self.cursor_to_char(cursor); + self.text.remove(index..=index); + } + + pub fn insert_char(&mut self, cursor: Cursor, c: char) { + let index = self.cursor_to_char(cursor); + self.text.insert_char(index, c); + } + + pub fn clamped_cursor(&self, cursor: Cursor) -> Cursor { + Cursor { + line: cursor.line, + column: cursor + .column + .min(self.text.line(cursor.line).len_chars() - 1), + } + } + + pub fn cursor_to_char(&self, cursor: Cursor) -> usize { + let cursor = self.clamped_cursor(cursor); + self.text.line_to_char(cursor.line) + cursor.column + } + + pub fn move_cursor(&self, cursor: &mut Cursor, direction: Direction, enable_linewrap: bool) { + *cursor = self.clamped_cursor(*cursor); + match direction { + Direction::Left => { + if cursor.column > 0 { + cursor.column -= 1; + } else if enable_linewrap && cursor.line > 0 { + cursor.line -= 1; + let line = self.text.line(cursor.line); + cursor.column = line.len_chars() - 1; + } + } + Direction::Down => { + if cursor.line + 2 < self.text.len_lines() { + cursor.line += 1; + } + } + Direction::Up => { + if cursor.line > 0 { + cursor.line -= 1; + } + } + Direction::Right => { + let line = self.text.line(cursor.line); + if cursor.column + 2 > line.len_chars() { + if enable_linewrap && cursor.line + 2 < self.text.len_lines() { + cursor.line += 1; + cursor.column = 0; + } + } else { + cursor.column += 1; + } + } + } + } +} diff --git a/src/main.rs b/src/main.rs index c814eaa..6992423 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,114 +29,14 @@ use crossterm::{ event::{read, Event, KeyCode, KeyEvent}, terminal, ExecutableCommand, Result, }; -use ropey::Rope; use yacexits::{exit, EX_DATAERR, EX_UNAVAILABLE}; +mod buffer; mod theme; +use buffer::Buffer; use theme::StyleStore; -struct Buffer { - pub text: Rope, -} - -impl Buffer { - pub fn from_str(text: &str) -> Self { - Self { - text: Rope::from_str(text), - } - } - - pub fn draw( - &self, - styles: &mut StyleStore, - cols: u16, - rows: u16, - scroll: Cursor, - out: &mut (impl ExecutableCommand + Write), - ) -> Result { - let linenr_style = styles.get_scope("ui.linenr"); - let linenr_width = self.text.len_lines().ilog10() + 1; - let gutter_width = linenr_width + 1; - let text_width = cols as usize - gutter_width as usize; - - out.execute(cursor::MoveTo(0, 0))?; - - for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) { - // only the last line is empty and should be skipped - if line.len_chars() == 0 { - break; - } - - let row = row as usize + scroll.line; - let linenr = format!("{:width$} ", row, width = linenr_width as usize); - linenr_style.print_styled(out, &linenr)?; - - let lhs = scroll.column; - let width = line.len_chars().saturating_sub(1); // lop off whitespace - if lhs < width { - let window = text_width.min(width - lhs); - let rhs = lhs + window; - write!(out, "{}", line.slice(lhs..rhs))?; - } - - out.execute(cursor::MoveToNextLine(1))?; - } - - Ok(gutter_width) - } - - pub fn clamped_cursor(&self, cursor: Cursor) -> Cursor { - Cursor { - line: cursor.line, - column: cursor - .column - .min(self.text.line(cursor.line).len_chars() - 1), - } - } - - pub fn cursor_to_char(&self, cursor: Cursor) -> usize { - let cursor = self.clamped_cursor(cursor); - self.text.line_to_char(cursor.line) + cursor.column - } - - pub fn move_cursor(&self, cursor: &mut Cursor, direction: Direction, enable_linewrap: bool) { - *cursor = self.clamped_cursor(*cursor); - match direction { - Direction::Left => { - if cursor.column > 0 { - cursor.column -= 1; - } else if enable_linewrap && cursor.line > 0 { - cursor.line -= 1; - let line = self.text.line(cursor.line); - cursor.column = line.len_chars() - 1; - } - } - Direction::Down => { - if cursor.line + 2 < self.text.len_lines() { - cursor.line += 1; - } - } - Direction::Up => { - if cursor.line > 0 { - cursor.line -= 1; - } - } - Direction::Right => { - let line = self.text.line(cursor.line); - if cursor.column + 2 > line.len_chars() { - if enable_linewrap && cursor.line + 2 < self.text.len_lines() { - cursor.line += 1; - cursor.column = 0; - } - } else { - cursor.column += 1; - } - } - } - } -} - #[derive(Copy, Clone, Debug, Default)] struct Cursor { pub column: usize, @@ -364,22 +264,18 @@ impl State { match event { Event::Key(KeyEvent { code, .. }) => match code { KeyCode::Char(c) => { - let index = self.buffer.cursor_to_char(self.cursor); - self.buffer.text.insert_char(index, c); + self.buffer.insert_char(self.cursor, c); self.move_cursor(Direction::Right) } KeyCode::Backspace => { self.move_cursor(Direction::Left); - let index = self.buffer.cursor_to_char(self.cursor); - self.buffer.text.remove(index..=index); + self.buffer.remove(self.cursor); } KeyCode::Delete => { - let index = self.buffer.cursor_to_char(self.cursor); - self.buffer.text.remove(index..=index); + self.buffer.remove(self.cursor); } KeyCode::Enter => { - let index = self.buffer.cursor_to_char(self.cursor); - self.buffer.text.insert_char(index, '\n'); + self.buffer.insert_char(self.cursor, '\n'); self.cursor.line += 1; self.cursor.column = 0; }