diff --git a/editor-challenge/src/main.rs b/editor-challenge/src/main.rs index f56ad64..19bcf67 100644 --- a/editor-challenge/src/main.rs +++ b/editor-challenge/src/main.rs @@ -1,10 +1,9 @@ +mod ui; mod state; mod types; -use crossterm::{ - event::{self, KeyCode, KeyEvent, KeyModifiers}, - terminal::{disable_raw_mode, enable_raw_mode}, -}; +use ui::Canvas; +use crossterm::event::{self, KeyCode, KeyEvent, KeyModifiers}; use state::AppState; use std::{ env, io, @@ -12,80 +11,12 @@ use std::{ thread, time::{Duration, Instant}, }; -use tui::{ - backend::CrosstermBackend, - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Style}, - widgets::{Block, BorderType, Borders, Paragraph}, - Terminal, -}; +use tui::{backend::CrosstermBackend, Terminal}; // const TITLE: &str = "Text Editor Challenge"; -const COPYRIGHT: &str = "(c) Savanni D'Gerinel - all rights reserved"; +// const COPYRIGHT: &str = "(c) Savanni D'Gerinel - all rights reserved"; const TICK_RATE_MS: u64 = 200; -fn render(app_state: &AppState, terminal: &mut Terminal) -> Result<(), anyhow::Error> -where - T: tui::backend::Backend, -{ - terminal.draw(|rect| { - let size = rect.size(); - let chunks = Layout::default() - .direction(Direction::Vertical) - .margin(2) - .constraints( - [ - Constraint::Length(3), - Constraint::Min(2), - Constraint::Length(3), - ] - .as_ref(), - ) - .split(size); - - let title = Paragraph::new( - app_state - .path - .clone() - .map(|path| path.to_string_lossy().into_owned()) - .unwrap_or("No file opened".to_owned()), - ) - .style(Style::default().fg(Color::LightCyan)) - .alignment(Alignment::Center) - .block( - Block::default() - .borders(Borders::ALL) - .style(Style::default().fg(Color::White)) - .title("Copyright") - .border_type(BorderType::Plain), - ); - rect.render_widget(title, chunks[0]); - - let cp = Paragraph::new(COPYRIGHT) - .style(Style::default().fg(Color::LightCyan)) - .alignment(Alignment::Center) - .block( - Block::default() - .borders(Borders::ALL) - .style(Style::default().fg(Color::White)) - .title("Copyright") - .border_type(BorderType::Plain), - ); - rect.render_widget(cp, chunks[2]); - - // TODO: rework scrolling as soon as I have a working cursor - let contents = Paragraph::new(app_state.contents.contents()).scroll((0, 0)); - rect.render_widget(contents, chunks[1]); - - // TODO: keeping track of the index of the top row and subtract that from the cursor row. - let (row, column) = app_state.cursor.addr(); - let row = row as u16; - let column = column as u16; - rect.set_cursor(chunks[1].x + column, chunks[1].y + row); - })?; - Ok(()) -} - #[derive(Debug)] enum Event { Input(I), @@ -113,14 +44,14 @@ fn handle_input(tx: mpsc::Sender>, tick_rate: Duration) { fn app_loop( mut app_state: AppState, - terminal: &mut Terminal, + mut screen: Canvas, rx: mpsc::Receiver>, ) -> Result<(), anyhow::Error> where T: tui::backend::Backend, { loop { - render(&app_state, terminal)?; + screen.render(&app_state)?; match rx.recv()? { Event::Input(event) @@ -152,17 +83,14 @@ fn main() -> Result<(), anyhow::Error> { handle_input(tx, tick_rate); }); - enable_raw_mode()?; - let stdout = io::stdout(); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; terminal.clear()?; - let result = app_loop(app_state, &mut terminal, rx); + let screen = Canvas::new(terminal); - disable_raw_mode()?; - terminal.show_cursor()?; + let result = app_loop(app_state, screen, rx); result?; diff --git a/editor-challenge/src/types.rs b/editor-challenge/src/types.rs index 2d52271..82e4b33 100644 --- a/editor-challenge/src/types.rs +++ b/editor-challenge/src/types.rs @@ -5,6 +5,9 @@ pub struct Document { impl Document { pub fn new(contents: Vec) -> Self { + if contents.len() > (u16::MAX.into()) { + panic!("Document row count exceeds u16::MAX. The current scrolling code cannot handle that."); + } Self{ rows: contents } diff --git a/editor-challenge/src/ui.rs b/editor-challenge/src/ui.rs new file mode 100644 index 0000000..f4f04ee --- /dev/null +++ b/editor-challenge/src/ui.rs @@ -0,0 +1,91 @@ +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use tui::{layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Block, BorderType, Borders, Paragraph}, Terminal}; + +use crate::state::AppState; + +pub struct Canvas { + top_row: usize, + terminal: Terminal, +} + +impl Canvas { + pub fn new(terminal: Terminal) -> Self { + enable_raw_mode().unwrap(); + Self { + top_row: 0, + terminal + } + } + + pub fn render(&mut self, app_state: &AppState) -> Result<(), anyhow::Error> + { + self.terminal.draw(|rect| { + let size = rect.size(); + let chunks = Layout::default() + .direction(Direction::Vertical) + .margin(2) + .constraints( + [ + Constraint::Min(2), + Constraint::Length(3), + // Constraint::Length(3), + ] + .as_ref(), + ) + .split(size); + + let title = Paragraph::new( + app_state + .path + .clone() + .map(|path| path.to_string_lossy().into_owned()) + .unwrap_or("No file opened".to_owned()), + ) + .style(Style::default().fg(Color::LightCyan)) + .alignment(Alignment::Center) + .block( + Block::default() + .borders(Borders::ALL) + .style(Style::default().fg(Color::White)) + .border_type(BorderType::Plain), + ); + rect.render_widget(title, chunks[1]); + + /* + let cp = Paragraph::new(COPYRIGHT) + .style(Style::default().fg(Color::LightCyan)) + .alignment(Alignment::Center) + .block( + Block::default() + .borders(Borders::ALL) + .style(Style::default().fg(Color::White)) + .title("Copyright") + .border_type(BorderType::Plain), + ); + rect.render_widget(cp, chunks[2]); + */ + + let (row, column) = app_state.cursor.addr(); + if row == self.top_row && row >= 1 { + self.top_row -= 1; + } else if row - self.top_row == (chunks[0].height - 1).into() { + self.top_row += 1; + } + + let contents = Paragraph::new(app_state.contents.contents()).scroll((self.top_row as u16, 0)); + rect.render_widget(contents, chunks[0]); + + rect.set_cursor(chunks[0].x + column as u16, chunks[0].y + (row - self.top_row) as u16); + })?; + Ok(()) + } +} + +impl Drop for Canvas { + fn drop(&mut self) { + let _ = disable_raw_mode(); + let _ = self.terminal.show_cursor(); + } +} + +