/* * 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 std::sync::Arc; use crossterm::{cursor, QueueableCommand}; use parking_lot::Mutex; use ropey::{Rope, RopeSlice}; use syntect::easy::ScopeRangeIterator; use syntect::parsing::{BasicScopeStackOp, ParseState, ScopeStack, SyntaxSet}; use crate::theme::{Style, StyleStore, StyledString}; use crate::{Cursor, Direction}; pub struct LineBuf<'a> { pub buf: Vec, pub padding: &'a str, pub remaining_width: usize, pub styles: &'a mut StyleStore, pub is_selected: bool, pub text: RopeSlice<'a>, pub styled: &'a StyledString, } impl<'a> LineBuf<'a> { pub fn draw_linenr(&mut self, linenr: usize, width: usize) -> crossterm::Result<()> { let linenr_style = self.styles.get_scope("ui.linenr").clone(); let linenr_style = if self.is_selected { self.styles.get_scope("ui.linenr.selected").clone() } else { linenr_style }; let linenr = if self.text.len_chars() > 0 { format!("{:>width$} ", linenr) } else { // only the last line is empty format!("{:>width$}", "~") }; linenr_style.print_styled(&mut self.buf, &linenr)?; self.remaining_width -= width; Ok(()) } pub fn draw_text(&mut self, lhs: usize) -> crossterm::Result<()> { let line_style = if self.is_selected { self.styles.get_scope("ui.cursorline.primary").clone() } else { Default::default() }; let remaining = if lhs < self.remaining_width { let sub = lhs..self.remaining_width; self.styled.print_sub(&mut self.buf, sub, line_style)? } else { self.remaining_width }; let padding = &self.padding[0..remaining]; line_style.print_styled(&mut self.buf, padding)?; Ok(()) } } #[derive(Clone, Debug)] pub struct Buffer { styles: Arc>, text: Rope, syntax_set: Arc, parser: ParseState, style_dirty: bool, style_generation: usize, styled: Vec, } impl AsRef for Buffer { fn as_ref(&self) -> &Rope { &self.text } } impl Buffer { pub fn from_str(styles: Arc>, text: &str) -> Self { let syntax_set = SyntaxSet::load_defaults_nonewlines(); // TODO load non-Rust syntax highlighting let syntax_ref = syntax_set.find_syntax_by_extension("rs").unwrap(); let parser = ParseState::new(syntax_ref); let mut buf = Self { styles, text: Rope::from_str(text), syntax_set: Arc::new(SyntaxSet::load_defaults_newlines()), parser, style_dirty: true, style_generation: 0, styled: Vec::new(), }; buf.parse(); buf } pub fn draw( &mut self, cols: u16, rows: u16, scroll: Cursor, cursor: Cursor, out: &mut impl Write, ) -> crossterm::Result { self.parse(); let mut styles = self.styles.lock(); let linenr_width = (self.text.len_lines().ilog10() + 1) as usize; let gutter_width = linenr_width as u32 + 1; let text_width = cols as usize - linenr_width; let line_padding: String = std::iter::repeat(' ').take(text_width).collect(); out.queue(cursor::MoveTo(0, 0))?; for (row, line) in self.text.lines_at(scroll.line).enumerate() { if row == rows as usize { break; } let linenr = row as usize + scroll.line; let mut line = LineBuf { buf: Vec::with_capacity(text_width * 2), padding: &line_padding, remaining_width: cols as usize - 1, styles: &mut styles, is_selected: linenr == cursor.line, text: line, styled: &self.styled[linenr], }; line.draw_linenr(linenr, linenr_width)?; line.draw_text(scroll.column)?; out.write_all(&line.buf)?; out.queue(cursor::MoveToNextLine(1))?; } Ok(gutter_width) } pub fn remove_char(&mut self, cursor: Cursor) { let index = self.cursor_to_char(cursor); if index < self.text.len_chars() { self.text.remove(index..=index); self.style_dirty = true; } } pub fn insert_char(&mut self, cursor: Cursor, c: char) { let index = self.cursor_to_char(cursor); self.text.insert_char(index, c); self.style_dirty = true; } fn parse(&mut self) { use std::fmt::Write; let mut styles = self.styles.lock(); if styles.generation() == self.style_generation && !self.style_dirty { return; } self.styled.clear(); let mut parser = self.parser.clone(); let mut line_buf = String::new(); let mut stack = ScopeStack::new(); let mut styled_buf = Vec::new(); let mut style_stack = Vec::