Refactor text rendering
This commit is contained in:
parent
15b6739586
commit
cce42ef240
148
src/buffer.rs
148
src/buffer.rs
|
@ -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;
|
||||
|
|
59
src/theme.rs
59
src/theme.rs
|
@ -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>,
|
||||
|
|
Loading…
Reference in New Issue