123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- use std::borrow::Cow;
- use std::cell::RefCell;
- use std::collections::HashMap;
- use std::rc::Rc;
- use std::str::FromStr;
- use crate::modules::perferences::Perferences;
- use crate::plugin::system::PluginSystem;
- use crate::view::colors::to_rgb;
- use crate::{buffer::Buffer, util::line_iterator::LineIterator, view::terminal::Terminal};
- use crate::{errors::*, get_application};
- use crossterm::style::Color;
- use held_core::plugin::Plugin;
- use held_core::utils::position::Position;
- use held_core::utils::range::Range;
- use held_core::view::colors::Colors;
- use held_core::view::render::ContentRenderBuffer;
- use held_core::view::style::CharStyle;
- use syntect::highlighting::{HighlightIterator, Highlighter, Style, Theme};
- use syntect::parsing::{ScopeStack, SyntaxSet};
- use unicode_segmentation::UnicodeSegmentation;
- use super::line_number_string_iter::LineNumberStringIter;
- use super::render_buffer::Cell;
- use super::render_state::RenderState;
- use super::{lexeme_mapper::LexemeMapper, render_buffer::RenderBuffer};
- const RENDER_CACHE_FREQUENCY: usize = 100;
- pub struct Renderer<'a, 'p> {
- buffer: &'a Buffer,
- render_buffer: &'a mut RenderBuffer<'p>,
- terminal: &'a dyn Terminal,
- theme: &'a Theme,
- highlight_ranges: Option<&'a [(Range, CharStyle, Colors)]>,
- scroll_offset: usize,
- line_number_iter: LineNumberStringIter,
- content_start_of_line: usize,
- cached_render_state: &'a Rc<RefCell<HashMap<usize, RenderState>>>,
- syntax_set: &'a SyntaxSet,
- screen_position: Position,
- buffer_position: Position,
- cursor_position: Option<Position>,
- current_style: Style,
- perferences: &'a dyn Perferences,
- plugin_system: &'a mut PluginSystem,
- }
- impl<'a, 'p> Renderer<'a, 'p> {
- pub fn new(
- buffer: &'a Buffer,
- render_buffer: &'a mut RenderBuffer<'p>,
- terminal: &'a dyn Terminal,
- perferences: &'a dyn Perferences,
- highlight_ranges: Option<&'a [(Range, CharStyle, Colors)]>,
- cached_render_state: &'a Rc<RefCell<HashMap<usize, RenderState>>>,
- theme: &'a Theme,
- syntax_set: &'a SyntaxSet,
- scroll_offset: usize,
- plugin_system: &'a mut PluginSystem,
- ) -> Renderer<'a, 'p> {
- let line_number_iter = LineNumberStringIter::new(buffer, scroll_offset);
- let content_start_of_line = line_number_iter.width() + 1;
- Self {
- buffer,
- render_buffer,
- terminal,
- theme,
- scroll_offset,
- syntax_set,
- cached_render_state,
- screen_position: Position::default(),
- buffer_position: Position::default(),
- cursor_position: None,
- current_style: Style::default(),
- perferences,
- highlight_ranges,
- line_number_iter,
- content_start_of_line,
- plugin_system,
- }
- }
- pub fn render(
- &mut self,
- lines: LineIterator<'a>,
- mut lexeme_mapper: Option<&mut dyn LexemeMapper>,
- ) -> Result<Option<Position>> {
- self.terminal.set_cursor(None)?;
- self.render_line_number();
- let highlighter = Highlighter::new(&self.theme);
- let syntax_definition = self
- .buffer
- .syntax_definition
- .as_ref()
- .ok_or("Buffer has no syntax definition")?;
- let focused_style = Renderer::mapper_keyword_style(&highlighter);
- let blurred_style = Renderer::mapper_comment_style(&highlighter);
- let (cached_line_num, mut state) = self
- .current_cached_render_state()
- .unwrap_or((0, RenderState::new(&highlighter, syntax_definition)));
- for (line_num, line_data) in lines {
- if line_num >= cached_line_num {
- if line_num % RENDER_CACHE_FREQUENCY == 0 {
- self.cached_render_state
- .borrow_mut()
- .insert(line_num, state.clone());
- }
- if self.before_visible() {
- self.try_to_advance_to_next_line(&line_data);
- continue;
- }
- if self.after_visible() {
- break;
- }
- let events = state
- .parse
- .parse_line(&line_data, self.syntax_set)
- .chain_err(|| "Failed to parse buffer")?;
- let styled_lexemes =
- HighlightIterator::new(&mut state.highlight, &events, &line_data, &highlighter);
- for (style, lexeme) in styled_lexemes {
- if let Some(ref mut mapper) = lexeme_mapper {
- let mapped_lexemes = mapper.map(lexeme, self.buffer_position);
- for mapped_lexeme in mapped_lexemes {
- match mapped_lexeme {
- super::lexeme_mapper::MappedLexeme::Focused(val) => {
- self.current_style = focused_style;
- self.render_lexeme(val.to_string());
- }
- super::lexeme_mapper::MappedLexeme::Blurred(val) => {
- self.current_style = blurred_style;
- self.render_lexeme(val.to_string());
- }
- }
- }
- } else {
- self.current_style = style;
- self.render_lexeme(lexeme);
- }
- }
- }
- self.try_to_advance_to_next_line(&line_data);
- }
- self.render_plugins()?;
- Ok(self.cursor_position)
- }
- fn mapper_keyword_style(highlighter: &Highlighter) -> Style {
- highlighter.style_for_stack(
- ScopeStack::from_str("keyword")
- .unwrap_or_default()
- .as_slice(),
- )
- }
- fn render_plugins(&mut self) -> Result<()> {
- let plugin_buffers = self.plugin_system.on_render_content();
- for plugin_buffer in plugin_buffers {
- self.render_plugin(plugin_buffer)?;
- }
- Ok(())
- }
- fn render_plugin(&mut self, buffer: ContentRenderBuffer) -> Result<()> {
- let mut line = 0;
- let mut offset = 0;
- let init_pos = buffer.rectangle.position;
- warn!("plugin cells {:?}", buffer.cells);
- for cell in buffer.cells {
- if let Some(cell) = cell {
- self.render_cell(
- Position {
- line: init_pos.line + line,
- offset: init_pos.offset + offset,
- },
- cell.style,
- cell.colors,
- cell.content.to_string(),
- );
- }
- offset += 1;
- if offset == buffer.rectangle.width {
- offset = 0;
- line += 1;
- }
- }
- Ok(())
- }
- fn mapper_comment_style(highlighter: &Highlighter) -> Style {
- highlighter.style_for_stack(
- ScopeStack::from_str("keyword")
- .unwrap_or_default()
- .as_slice(),
- )
- }
- fn current_cached_render_state(&self) -> Option<(usize, RenderState)> {
- self.cached_render_state
- .borrow()
- .iter()
- .filter(|(k, _)| **k < self.scroll_offset)
- .max_by(|a, b| a.0.cmp(b.0))
- .map(|x| (*x.0, x.1.clone()))
- }
- fn after_visible(&self) -> bool {
- self.screen_position.line >= (self.terminal.height().unwrap() - 1)
- }
- fn before_visible(&self) -> bool {
- self.buffer_position.line < self.scroll_offset
- }
- fn inside_visible(&self) -> bool {
- !self.before_visible() && !self.after_visible()
- }
- fn set_cursor(&mut self) {
- if self.inside_visible() && *self.buffer.cursor == self.buffer_position {
- self.cursor_position = Some(self.screen_position);
- get_application().state_data.cursor_state.screen_position = self.screen_position;
- }
- }
- fn on_cursor_line(&self) -> bool {
- self.buffer.cursor.line == self.buffer_position.line
- }
- fn try_to_advance_to_next_line(&mut self, line: &str) {
- if line.chars().last().map(|x| x == '\n').unwrap_or(false) {
- self.advance_to_next_line();
- }
- }
- fn advance_to_next_line(&mut self) {
- if self.inside_visible() {
- self.set_cursor();
- self.render_rest_of_line();
- self.screen_position.line += 1;
- }
- self.buffer_position.line += 1;
- self.buffer_position.offset = 0;
- self.render_line_number();
- }
- fn render_rest_of_line(&mut self) {
- let on_cursor_line = self.on_cursor_line();
- for offset in self.screen_position.offset..self.terminal.width().unwrap() {
- let colors = if on_cursor_line {
- Colors::Focused
- } else {
- Colors::Default
- };
- self.render_cell(
- Position {
- line: self.screen_position.line,
- offset,
- },
- CharStyle::Default,
- colors,
- " ",
- );
- }
- }
- fn render_line_number(&mut self) {
- if !self.inside_visible() {
- return;
- }
- let line_number = self.line_number_iter.next().unwrap();
- let is_on_cursor_line = self.on_cursor_line();
- let style = if is_on_cursor_line {
- CharStyle::Bold
- } else {
- CharStyle::Default
- };
- // 渲染行号
- self.render_cell(
- Position {
- line: self.screen_position.line,
- offset: 0,
- },
- style,
- Colors::Focused,
- line_number,
- );
- // 行号后的gap
- let gap_color = if is_on_cursor_line {
- Colors::Focused
- } else {
- Colors::Default
- };
- self.render_cell(
- Position {
- line: self.screen_position.line,
- offset: self.line_number_iter.width(),
- },
- style,
- gap_color,
- " ",
- );
- self.screen_position.offset = self.line_number_iter.width() + 1;
- }
- fn render_lexeme<T: Into<Cow<'a, str>>>(&mut self, lexeme: T) {
- for character in lexeme.into().graphemes(true) {
- if character == "\n" {
- continue;
- }
- self.set_cursor();
- let token_color = to_rgb(self.current_style.foreground);
- let (style, color) = self.current_char_style(token_color);
- if self.perferences.line_wrapping()
- && self.screen_position.offset == self.terminal.width().unwrap() - 1
- {
- self.render_cell(self.screen_position, style, color, character.to_string());
- self.buffer_position.offset += 1;
- // 屏幕上换行但是渲染原来的line
- let prefix_len = self.content_start_of_line;
- let prefix = " ".repeat(prefix_len);
- self.screen_position.offset = 0;
- self.screen_position.line += 1;
- self.render_cell(
- Position {
- line: self.screen_position.line,
- offset: self.screen_position.offset,
- },
- style,
- Colors::Default,
- prefix,
- );
- self.screen_position.offset += prefix_len;
- } else if character == "\t" {
- let tab_len = self.perferences.tab_width();
- let width = tab_len - (self.screen_position.offset + 1) % tab_len;
- let tab_str = " ".repeat(width);
- self.render_lexeme(tab_str);
- } else {
- self.render_cell(self.screen_position, style, color, character.to_string());
- self.screen_position.offset += 1;
- self.buffer_position.offset += 1;
- }
- // 退出循环前更新
- self.set_cursor();
- }
- }
- fn render_cell<C: Into<Cow<'p, str>>>(
- &mut self,
- position: Position,
- style: CharStyle,
- colors: Colors,
- content: C,
- ) {
- self.render_buffer.set_cell(
- position,
- Cell {
- content: content.into(),
- colors,
- style,
- },
- );
- }
- fn current_char_style(&self, token_color: Color) -> (CharStyle, Colors) {
- let (style, colors) = match self.highlight_ranges {
- Some(highlight_ranges) => {
- for (range, style, colors) in highlight_ranges {
- if range.includes(&self.buffer_position) {
- // 修正背景色
- let fix_colors = if let Colors::CustomForeground(color) = colors {
- if self.on_cursor_line() {
- Colors::CustomFocusedForeground(*color)
- } else {
- *colors
- }
- } else {
- *colors
- };
- return (*style, fix_colors);
- }
- }
- // We aren't inside one of the highlighted areas.
- // Fall back to other styling considerations.
- if self.on_cursor_line() {
- (
- CharStyle::Default,
- Colors::CustomFocusedForeground(token_color),
- )
- } else {
- (CharStyle::Default, Colors::CustomForeground(token_color))
- }
- }
- None => {
- if self.on_cursor_line() {
- (
- CharStyle::Default,
- Colors::CustomFocusedForeground(token_color),
- )
- } else {
- (CharStyle::Default, Colors::CustomForeground(token_color))
- }
- }
- };
- (style, colors)
- }
- }
- #[cfg(test)]
- mod tests {
- use std::{
- cell::RefCell,
- collections::HashMap,
- io::{BufReader, Cursor},
- path::Path,
- rc::Rc,
- };
- use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
- use crate::{
- buffer::Buffer,
- modules::perferences::DummyPerferences,
- util::line_iterator::LineIterator,
- view::{
- colors::map::ColorMap,
- render::render_buffer::{CachedRenderBuffer, RenderBuffer},
- terminal::{cross_terminal::CrossTerminal, Terminal},
- },
- };
- use super::Renderer;
- #[test]
- fn test_display() {
- let terminal = CrossTerminal::new().unwrap();
- let mut buffer = Buffer::from_file(Path::new("src/main.rs")).unwrap();
- let mut render_buffer = RenderBuffer::new(
- terminal.width().unwrap(),
- terminal.height().unwrap(),
- Rc::new(RefCell::new(CachedRenderBuffer::new(
- terminal.width().unwrap(),
- terminal.height().unwrap(),
- ))),
- );
- let perferences = DummyPerferences;
- let cached_render_state = Rc::new(RefCell::new(HashMap::new()));
- let mut reader = BufReader::new(Cursor::new(include_str!(
- "../../themes/solarized_dark.tmTheme"
- )));
- let theme = ThemeSet::load_from_reader(&mut reader).unwrap();
- let syntax_set = SyntaxSet::load_defaults_newlines();
- let definition = buffer
- .file_extension()
- .and_then(|ex| syntax_set.find_syntax_by_extension(&ex))
- .or_else(|| Some(syntax_set.find_syntax_plain_text()))
- .cloned();
- buffer.syntax_definition = definition;
- let binding = buffer.data();
- {
- let mut renderer = Renderer::new(
- &buffer,
- &mut render_buffer,
- &terminal,
- &perferences,
- None,
- &cached_render_state,
- &theme,
- &syntax_set,
- 0,
- todo!(),
- );
- renderer.render(LineIterator::new(&binding), None).unwrap();
- }
- for (position, cell) in render_buffer.iter() {
- terminal
- .print(
- &position,
- cell.style,
- theme.map_colors(cell.colors),
- &cell.content,
- )
- .unwrap();
- }
- terminal.present().unwrap();
- }
- }
|