Refactor text rendering

This commit is contained in:
mars 2023-09-14 17:19:51 -06:00
parent 15b6739586
commit cce42ef240
2 changed files with 143 additions and 64 deletions

View File

@ -18,18 +18,71 @@
*/
use std::io::Write;
use std::ops::Range;
use std::sync::Arc;
use crossterm::{cursor, QueueableCommand};
use parking_lot::Mutex;
use ropey::Rope;
use ropey::{Rope, RopeSlice};
use syntect::easy::ScopeRangeIterator;
use syntect::parsing::{BasicScopeStackOp, ParseState, ScopeStack, SyntaxSet};
use crate::theme::{Style, StyleStore};
use crate::theme::{Style, StyleStore, StyledString};
use crate::{Cursor, Direction};
pub struct LineBuf<'a> {
pub buf: Vec<u8>,
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)?
} 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<Mutex<StyleStore>>,
@ -38,7 +91,7 @@ pub struct Buffer {
parser: ParseState,
style_dirty: bool,
style_generation: usize,
styled: Vec<Vec<(Style, Range<usize>)>>,
styled: Vec<StyledString>,
}
impl AsRef<Rope> for Buffer {
@ -80,70 +133,33 @@ impl Buffer {
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 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))?;
let mut line_buf = Vec::<u8>::with_capacity(text_width);
for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) {
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 linenr = if line.len_chars() > 0 {
format!("{:>width$} ", row, width = linenr_width as usize)
} else {
// only the last line is empty
format!("{:>width$}", "~", width = linenr_width as usize)
};
let line_style = if is_selected {
styles.get_scope("ui.cursorline.primary").clone()
} else {
Default::default()
};
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;
}
}
for (row, line) in self.text.lines_at(scroll.line).enumerate() {
if row == rows as usize {
break;
}
let padding = &line_padding[0..remaining];
line_style.print_styled(&mut line_buf, padding)?;
let linenr = row as usize + scroll.line;
out.write_all(&line_buf)?;
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))?;
}
@ -213,11 +229,17 @@ impl Buffer {
continue;
}
let len = range.end - range.start;
let style = style_stack.last().cloned().unwrap_or_default();
styled_buf.push((style, range));
styled_buf.push((len, style));
}
self.styled.push(styled_buf.clone());
let line = StyledString {
content: line.to_string(),
styles: styled_buf.clone(),
};
self.styled.push(line);
}
self.style_dirty = false;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
use std::{collections::HashMap, fmt::Display};
use std::{collections::HashMap, fmt::Display, ops::Range};
use crossterm::style::Color;
use crossterm::QueueableCommand;
@ -122,6 +122,63 @@ impl StyleStore {
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StyledString {
pub content: String,
pub styles: Vec<(usize, Style)>,
}
impl StyledString {
pub fn new_unstyled(content: String) -> Self {
Self {
styles: vec![(content.len(), Style::default())],
content,
}
}
pub fn print(&self, out: &mut impl QueueableCommand) -> crossterm::Result<()> {
let mut cursor = 0;
for (size, style) in self.styles.iter() {
let end = cursor + size;
let range = cursor..end;
let sub = &self.content[range];
style.print_styled(out, sub)?;
cursor = end;
}
Ok(())
}
pub fn print_sub(
&self,
out: &mut impl QueueableCommand,
clip: Range<usize>,
) -> crossterm::Result<usize> {
let mut remaining = clip.end - clip.start;
let mut cursor = 0;
for (size, style) in self.styles.iter() {
let end = cursor + size;
let clipped = (cursor.max(clip.start))..(end.min(clip.end));
cursor = end;
if clipped.start >= clipped.end {
continue;
}
remaining -= clipped.end - clipped.start;
let sub = &self.content[clipped];
style.print_styled(out, sub)?;
if cursor >= clip.end {
break;
}
}
Ok(remaining)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Style {
pub fg: Option<Color>,