/* * 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::ops::Range; use std::sync::Arc; use crossterm::{cursor, ExecutableCommand, QueueableCommand}; use parking_lot::Mutex; use ropey::Rope; use syntect::easy::ScopeRangeIterator; use syntect::parsing::{BasicScopeStackOp, ParseState, Scope, ScopeStack, SyntaxSet}; use crate::theme::{Style, StyleStore}; use crate::{Cursor, Direction}; #[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_style = styles.get_scope("ui.linenr").clone(); 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; let line_padding: String = std::iter::repeat(' ').take(text_width).collect(); out.queue(cursor::MoveTo(0, 0))?; let mut line_buf = Vec::::with_capacity(text_width); 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 is_selected = row == cursor.line; let linenr_style = if is_selected { styles.get_scope("ui.linenr.selected").clone() } else { linenr_style }; let line_style = if is_selected { styles.get_scope("ui.cursorline.primary").clone() } else { Default::default() }; 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(); let mut remaining = text_width; line_buf.clear(); if lhs < width { let window = text_width.min(width - lhs); let rhs = lhs + window; let styled = &self.styled[row]; for (chunk_style, range) in styled.iter() { let range = range.start.max(lhs)..range.end.min(rhs); if range.start < range.end { // this range is in view so display let content = line.slice(range.clone()); let mut style = line_style.clone(); style.apply(chunk_style); style.print_styled(&mut line_buf, content)?; remaining = text_width.saturating_sub(range.end); } if range.end >= rhs { // past the end of the window so we're done break; } } } let padding = &line_padding[0..remaining]; line_style.print_styled(&mut line_buf, padding)?; out.write_all(&line_buf)?; out.queue(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); 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::