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::io::Write;
|
||||||
use std::ops::Range;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crossterm::{cursor, QueueableCommand};
|
use crossterm::{cursor, QueueableCommand};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use ropey::Rope;
|
use ropey::{Rope, RopeSlice};
|
||||||
use syntect::easy::ScopeRangeIterator;
|
use syntect::easy::ScopeRangeIterator;
|
||||||
use syntect::parsing::{BasicScopeStackOp, ParseState, ScopeStack, SyntaxSet};
|
use syntect::parsing::{BasicScopeStackOp, ParseState, ScopeStack, SyntaxSet};
|
||||||
|
|
||||||
use crate::theme::{Style, StyleStore};
|
use crate::theme::{Style, StyleStore, StyledString};
|
||||||
use crate::{Cursor, Direction};
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
styles: Arc<Mutex<StyleStore>>,
|
styles: Arc<Mutex<StyleStore>>,
|
||||||
|
@ -38,7 +91,7 @@ pub struct Buffer {
|
||||||
parser: ParseState,
|
parser: ParseState,
|
||||||
style_dirty: bool,
|
style_dirty: bool,
|
||||||
style_generation: usize,
|
style_generation: usize,
|
||||||
styled: Vec<Vec<(Style, Range<usize>)>>,
|
styled: Vec<StyledString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Rope> for Buffer {
|
impl AsRef<Rope> for Buffer {
|
||||||
|
@ -80,70 +133,33 @@ impl Buffer {
|
||||||
self.parse();
|
self.parse();
|
||||||
|
|
||||||
let mut styles = self.styles.lock();
|
let mut styles = self.styles.lock();
|
||||||
let linenr_style = styles.get_scope("ui.linenr").clone();
|
let linenr_width = (self.text.len_lines().ilog10() + 1) as usize;
|
||||||
let linenr_width = self.text.len_lines().ilog10() + 1;
|
let gutter_width = linenr_width as u32 + 1;
|
||||||
let gutter_width = linenr_width + 1;
|
let text_width = cols as usize - linenr_width;
|
||||||
let text_width = cols as usize - gutter_width as usize;
|
|
||||||
let line_padding: String = std::iter::repeat(' ').take(text_width).collect();
|
let line_padding: String = std::iter::repeat(' ').take(text_width).collect();
|
||||||
|
|
||||||
out.queue(cursor::MoveTo(0, 0))?;
|
out.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
|
||||||
let mut line_buf = Vec::<u8>::with_capacity(text_width);
|
for (row, line) in self.text.lines_at(scroll.line).enumerate() {
|
||||||
for (row, line) in (0..rows).zip(self.text.lines_at(scroll.line)) {
|
if row == rows as usize {
|
||||||
let row = row as usize + scroll.line;
|
break;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let padding = &line_padding[0..remaining];
|
let linenr = row as usize + scroll.line;
|
||||||
line_style.print_styled(&mut line_buf, padding)?;
|
|
||||||
|
|
||||||
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))?;
|
out.queue(cursor::MoveToNextLine(1))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,11 +229,17 @@ impl Buffer {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let len = range.end - range.start;
|
||||||
let style = style_stack.last().cloned().unwrap_or_default();
|
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;
|
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/.
|
* 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::style::Color;
|
||||||
use crossterm::QueueableCommand;
|
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)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
pub fg: Option<Color>,
|
pub fg: Option<Color>,
|
||||||
|
|
Loading…
Reference in New Issue