mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
streamline text decoration API
This commit brings the text decoration API inline with the LineAnnotation API (so they are consistent) resulting in a single streamlined API instead of multiple ADHOK callbacks.
This commit is contained in:
parent
9a93240d27
commit
2c0506aa96
@ -290,7 +290,7 @@ async fn render(&mut self) {
|
|||||||
self.compositor.render(area, surface, &mut cx);
|
self.compositor.render(area, surface, &mut cx);
|
||||||
let (pos, kind) = self.compositor.cursor(area, &self.editor);
|
let (pos, kind) = self.compositor.cursor(area, &self.editor);
|
||||||
// reset cursor cache
|
// reset cursor cache
|
||||||
self.editor.cursor_cache.set(None);
|
self.editor.cursor_cache.reset();
|
||||||
|
|
||||||
let pos = pos.map(|pos| (pos.col as u16, pos.row as u16));
|
let pos = pos.map(|pos| (pos.col as u16, pos.row as u16));
|
||||||
self.terminal.draw(pos, kind).unwrap();
|
self.terminal.draw(pos, kind).unwrap();
|
||||||
|
@ -12,26 +12,10 @@
|
|||||||
use helix_view::graphics::Rect;
|
use helix_view::graphics::Rect;
|
||||||
use helix_view::theme::Style;
|
use helix_view::theme::Style;
|
||||||
use helix_view::view::ViewPosition;
|
use helix_view::view::ViewPosition;
|
||||||
use helix_view::Document;
|
use helix_view::{Document, Theme};
|
||||||
use helix_view::Theme;
|
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::buffer::Buffer as Surface;
|
||||||
|
|
||||||
pub trait LineDecoration {
|
use crate::ui::text_decorations::DecorationManager;
|
||||||
fn render_background(&mut self, _renderer: &mut TextRenderer, _pos: LinePos) {}
|
|
||||||
fn render_foreground(
|
|
||||||
&mut self,
|
|
||||||
_renderer: &mut TextRenderer,
|
|
||||||
_pos: LinePos,
|
|
||||||
_end_char_idx: usize,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F: FnMut(&mut TextRenderer, LinePos)> LineDecoration for F {
|
|
||||||
fn render_background(&mut self, renderer: &mut TextRenderer, pos: LinePos) {
|
|
||||||
self(renderer, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
enum StyleIterKind {
|
enum StyleIterKind {
|
||||||
@ -95,15 +79,8 @@ pub struct LinePos {
|
|||||||
pub doc_line: usize,
|
pub doc_line: usize,
|
||||||
/// Vertical offset from the top of the inner view area
|
/// Vertical offset from the top of the inner view area
|
||||||
pub visual_line: u16,
|
pub visual_line: u16,
|
||||||
/// The first char index of this visual line.
|
|
||||||
/// Note that if the visual line is entirely filled by
|
|
||||||
/// a very long inline virtual text then this index will point
|
|
||||||
/// at the next (non-virtual) char after this visual line
|
|
||||||
pub start_char_idx: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TranslatedPosition<'a> = (usize, Box<dyn FnMut(&mut TextRenderer, Position) + 'a>);
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn render_document(
|
pub fn render_document(
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
@ -114,84 +91,46 @@ pub fn render_document(
|
|||||||
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
||||||
overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
line_decoration: &mut [Box<dyn LineDecoration + '_>],
|
decorations: DecorationManager,
|
||||||
translated_positions: &mut [TranslatedPosition],
|
|
||||||
) {
|
) {
|
||||||
let mut renderer = TextRenderer::new(surface, doc, theme, offset.horizontal_offset, viewport);
|
let mut renderer = TextRenderer::new(
|
||||||
|
surface,
|
||||||
|
doc,
|
||||||
|
theme,
|
||||||
|
Position::new(offset.vertical_offset, offset.horizontal_offset),
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
render_text(
|
render_text(
|
||||||
&mut renderer,
|
&mut renderer,
|
||||||
doc.text().slice(..),
|
doc.text().slice(..),
|
||||||
offset,
|
offset.anchor,
|
||||||
&doc.text_format(viewport.width, Some(theme)),
|
&doc.text_format(viewport.width, Some(theme)),
|
||||||
doc_annotations,
|
doc_annotations,
|
||||||
syntax_highlight_iter,
|
syntax_highlight_iter,
|
||||||
overlay_highlight_iter,
|
overlay_highlight_iter,
|
||||||
theme,
|
theme,
|
||||||
line_decoration,
|
decorations,
|
||||||
translated_positions,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_positions(
|
|
||||||
char_pos: usize,
|
|
||||||
first_visible_char_idx: usize,
|
|
||||||
translated_positions: &mut [TranslatedPosition],
|
|
||||||
text_fmt: &TextFormat,
|
|
||||||
renderer: &mut TextRenderer,
|
|
||||||
pos: Position,
|
|
||||||
) {
|
|
||||||
// check if any positions translated on the fly (like cursor) has been reached
|
|
||||||
for (char_idx, callback) in &mut *translated_positions {
|
|
||||||
if *char_idx < char_pos && *char_idx >= first_visible_char_idx {
|
|
||||||
// by replacing the char_index with usize::MAX large number we ensure
|
|
||||||
// that the same position is only translated once
|
|
||||||
// text will never reach usize::MAX as rust memory allocations are limited
|
|
||||||
// to isize::MAX
|
|
||||||
*char_idx = usize::MAX;
|
|
||||||
|
|
||||||
if text_fmt.soft_wrap {
|
|
||||||
callback(renderer, pos)
|
|
||||||
} else if pos.col >= renderer.col_offset
|
|
||||||
&& pos.col - renderer.col_offset < renderer.viewport.width as usize
|
|
||||||
{
|
|
||||||
callback(
|
|
||||||
renderer,
|
|
||||||
Position {
|
|
||||||
row: pos.row,
|
|
||||||
col: pos.col - renderer.col_offset,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn render_text<'t>(
|
pub fn render_text<'t>(
|
||||||
renderer: &mut TextRenderer,
|
renderer: &mut TextRenderer,
|
||||||
text: RopeSlice<'t>,
|
text: RopeSlice<'_>,
|
||||||
offset: ViewPosition,
|
anchor: usize,
|
||||||
text_fmt: &TextFormat,
|
text_fmt: &TextFormat,
|
||||||
text_annotations: &TextAnnotations,
|
text_annotations: &TextAnnotations,
|
||||||
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
||||||
overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
line_decorations: &mut [Box<dyn LineDecoration + '_>],
|
mut decorations: DecorationManager,
|
||||||
translated_positions: &mut [TranslatedPosition],
|
|
||||||
) {
|
) {
|
||||||
let mut row_off = visual_offset_from_block(
|
let row_off = visual_offset_from_block(text, anchor, anchor, text_fmt, text_annotations)
|
||||||
text,
|
|
||||||
offset.anchor,
|
|
||||||
offset.anchor,
|
|
||||||
text_fmt,
|
|
||||||
text_annotations,
|
|
||||||
)
|
|
||||||
.0
|
.0
|
||||||
.row;
|
.row;
|
||||||
row_off += offset.vertical_offset;
|
|
||||||
|
|
||||||
let mut formatter =
|
let mut formatter =
|
||||||
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
|
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, anchor);
|
||||||
let mut syntax_styles = StyleIter {
|
let mut syntax_styles = StyleIter {
|
||||||
text_style: renderer.text_style,
|
text_style: renderer.text_style,
|
||||||
active_highlights: Vec::with_capacity(64),
|
active_highlights: Vec::with_capacity(64),
|
||||||
@ -213,8 +152,8 @@ pub fn render_text<'t>(
|
|||||||
first_visual_line: false,
|
first_visual_line: false,
|
||||||
doc_line: usize::MAX,
|
doc_line: usize::MAX,
|
||||||
visual_line: u16::MAX,
|
visual_line: u16::MAX,
|
||||||
start_char_idx: usize::MAX,
|
|
||||||
};
|
};
|
||||||
|
let mut last_line_end = 0;
|
||||||
let mut is_in_indent_area = true;
|
let mut is_in_indent_area = true;
|
||||||
let mut last_line_indent_level = 0;
|
let mut last_line_indent_level = 0;
|
||||||
let mut syntax_style_span = syntax_styles
|
let mut syntax_style_span = syntax_styles
|
||||||
@ -223,58 +162,22 @@ pub fn render_text<'t>(
|
|||||||
let mut overlay_style_span = overlay_styles
|
let mut overlay_style_span = overlay_styles
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or_else(|| (Style::default(), usize::MAX));
|
.unwrap_or_else(|| (Style::default(), usize::MAX));
|
||||||
let mut first_visible_char_idx = formatter.next_char_pos();
|
let mut reached_view_top = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// formattter.line_pos returns to line index of the next grapheme
|
|
||||||
// so it must be called before formatter.next
|
|
||||||
let Some(mut grapheme) = formatter.next() else {
|
let Some(mut grapheme) = formatter.next() else {
|
||||||
let mut last_pos = formatter.next_visual_pos();
|
|
||||||
if last_pos.row >= row_off {
|
|
||||||
last_pos.col -= 1;
|
|
||||||
last_pos.row -= row_off;
|
|
||||||
// check if any positions translated on the fly (like cursor) are at the EOF
|
|
||||||
translate_positions(
|
|
||||||
text.len_chars() + 1,
|
|
||||||
first_visible_char_idx,
|
|
||||||
translated_positions,
|
|
||||||
text_fmt,
|
|
||||||
renderer,
|
|
||||||
last_pos,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
// skip any graphemes on visual lines before the block start
|
// skip any graphemes on visual lines before the block start
|
||||||
// if pos.row < row_off {
|
|
||||||
// if char_pos >= syntax_style_span.1 {
|
|
||||||
// syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
|
|
||||||
// syntax_style_span
|
|
||||||
// } else {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if char_pos >= overlay_style_span.1 {
|
|
||||||
// overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
|
|
||||||
// overlay_style_span
|
|
||||||
if grapheme.visual_pos.row < row_off {
|
if grapheme.visual_pos.row < row_off {
|
||||||
if grapheme.char_idx >= style_span.1 {
|
|
||||||
style_span = if let Some(style_span) = styles.next() {
|
|
||||||
style_span
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
overlay_span = if let Some(overlay_span) = overlays.next() {
|
|
||||||
overlay_span
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
first_visible_char_idx = formatter.next_char_pos();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
grapheme.visual_pos.row -= row_off;
|
grapheme.visual_pos.row -= row_off;
|
||||||
|
if !reached_view_top {
|
||||||
|
decorations.prepare_for_rendering(grapheme.char_idx);
|
||||||
|
reached_view_top = true;
|
||||||
|
}
|
||||||
|
|
||||||
// if the end of the viewport is reached stop rendering
|
// if the end of the viewport is reached stop rendering
|
||||||
if grapheme.visual_pos.row as u16 >= renderer.viewport.height + renderer.offset.row as u16 {
|
if grapheme.visual_pos.row as u16 >= renderer.viewport.height + renderer.offset.row as u16 {
|
||||||
@ -283,24 +186,22 @@ pub fn render_text<'t>(
|
|||||||
|
|
||||||
// apply decorations before rendering a new line
|
// apply decorations before rendering a new line
|
||||||
if grapheme.visual_pos.row as u16 != last_line_pos.visual_line {
|
if grapheme.visual_pos.row as u16 != last_line_pos.visual_line {
|
||||||
if grapheme.visual_pos.row > 0 {
|
// we initiate doc_line with usize::MAX because no file
|
||||||
|
// can reach that size (memory allocations are limited to isize::MAX)
|
||||||
|
// initially there is no "previous" line (so doc_line is set to usize::MAX)
|
||||||
|
// in that case we don't need to draw indent guides/virtual text
|
||||||
|
if last_line_pos.doc_line != usize::MAX {
|
||||||
// draw indent guides for the last line
|
// draw indent guides for the last line
|
||||||
renderer
|
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
||||||
.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line as u16);
|
|
||||||
is_in_indent_area = true;
|
is_in_indent_area = true;
|
||||||
for line_decoration in &mut *line_decorations {
|
decorations.render_virtual_lines(renderer, last_line_pos, last_line_end)
|
||||||
line_decoration.render_foreground(renderer, last_line_pos, grapheme.char_idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
last_line_pos = LinePos {
|
last_line_pos = LinePos {
|
||||||
first_visual_line: grapheme.line_idx != last_line_pos.doc_line,
|
first_visual_line: grapheme.line_idx != last_line_pos.doc_line,
|
||||||
doc_line: grapheme.line_idx,
|
doc_line: grapheme.line_idx,
|
||||||
visual_line: grapheme.visual_pos.row as u16,
|
visual_line: grapheme.visual_pos.row as u16,
|
||||||
start_char_idx: grapheme.char_idx,
|
|
||||||
};
|
};
|
||||||
for line_decoration in &mut *line_decorations {
|
decorations.decorate_line(renderer, last_line_pos);
|
||||||
line_decoration.render_background(renderer, last_line_pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// acquire the correct grapheme style
|
// acquire the correct grapheme style
|
||||||
@ -315,55 +216,37 @@ pub fn render_text<'t>(
|
|||||||
.unwrap_or((Style::default(), usize::MAX));
|
.unwrap_or((Style::default(), usize::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if any positions translated on the fly (like cursor) has been reached
|
let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
|
||||||
translate_positions(
|
|
||||||
formatter.next_char_pos(),
|
|
||||||
first_visible_char_idx,
|
|
||||||
translated_positions,
|
|
||||||
text_fmt,
|
|
||||||
renderer,
|
|
||||||
grapheme.visual_pos,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (syntax_style, overlay_style) =
|
|
||||||
if let GraphemeSource::VirtualText { highlight } = grapheme.source {
|
|
||||||
let mut style = renderer.text_style;
|
let mut style = renderer.text_style;
|
||||||
if let Some(highlight) = highlight {
|
if let Some(highlight) = highlight {
|
||||||
style = style.patch(theme.highlight(highlight.0))
|
style = style.patch(theme.highlight(highlight.0));
|
||||||
}
|
}
|
||||||
(style, Style::default())
|
|
||||||
} else {
|
|
||||||
(syntax_style_span.0, overlay_style_span.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_virtual = grapheme.is_virtual();
|
|
||||||
renderer.draw_grapheme(
|
|
||||||
<<<<<<< HEAD
|
|
||||||
grapheme.grapheme,
|
|
||||||
GraphemeStyle {
|
GraphemeStyle {
|
||||||
syntax_style,
|
syntax_style: style,
|
||||||
overlay_style,
|
overlay_style: Style::default(),
|
||||||
},
|
}
|
||||||
is_virtual,
|
} else {
|
||||||
||||||| parent of 5e32edd8 (track char_idx in DocFormatter)
|
GraphemeStyle {
|
||||||
grapheme.grapheme,
|
syntax_style: syntax_style_span.0,
|
||||||
grapheme_style,
|
overlay_style: overlay_style_span.0,
|
||||||
virt,
|
}
|
||||||
=======
|
};
|
||||||
|
decorations.decorate_grapheme(renderer, &grapheme);
|
||||||
|
|
||||||
|
let virt = grapheme.is_virtual();
|
||||||
|
let grapheme_width = renderer.draw_grapheme(
|
||||||
grapheme.raw,
|
grapheme.raw,
|
||||||
grapheme_style,
|
grapheme_style,
|
||||||
virt,
|
virt,
|
||||||
>>>>>>> 5e32edd8 (track char_idx in DocFormatter)
|
|
||||||
&mut last_line_indent_level,
|
&mut last_line_indent_level,
|
||||||
&mut is_in_indent_area,
|
&mut is_in_indent_area,
|
||||||
grapheme.visual_pos,
|
grapheme.visual_pos,
|
||||||
);
|
);
|
||||||
|
last_line_end = grapheme.visual_pos.col + grapheme_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
||||||
for line_decoration in &mut *line_decorations {
|
decorations.render_virtual_lines(renderer, last_line_pos, last_line_end)
|
||||||
line_decoration.render_foreground(renderer, last_line_pos, formatter.next_char_pos());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -382,8 +265,8 @@ pub struct TextRenderer<'a> {
|
|||||||
pub indent_width: u16,
|
pub indent_width: u16,
|
||||||
pub starting_indent: usize,
|
pub starting_indent: usize,
|
||||||
pub draw_indent_guides: bool,
|
pub draw_indent_guides: bool,
|
||||||
pub col_offset: usize,
|
|
||||||
pub viewport: Rect,
|
pub viewport: Rect,
|
||||||
|
pub offset: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GraphemeStyle {
|
pub struct GraphemeStyle {
|
||||||
@ -396,7 +279,7 @@ pub fn new(
|
|||||||
surface: &'a mut Surface,
|
surface: &'a mut Surface,
|
||||||
doc: &Document,
|
doc: &Document,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
col_offset: usize,
|
offset: Position,
|
||||||
viewport: Rect,
|
viewport: Rect,
|
||||||
) -> TextRenderer<'a> {
|
) -> TextRenderer<'a> {
|
||||||
let editor_config = doc.config.load();
|
let editor_config = doc.config.load();
|
||||||
@ -451,8 +334,8 @@ pub fn new(
|
|||||||
virtual_tab,
|
virtual_tab,
|
||||||
whitespace_style: theme.get("ui.virtual.whitespace"),
|
whitespace_style: theme.get("ui.virtual.whitespace"),
|
||||||
indent_width,
|
indent_width,
|
||||||
starting_indent: col_offset / indent_width as usize
|
starting_indent: offset.col / indent_width as usize
|
||||||
+ (col_offset % indent_width as usize != 0) as usize
|
+ (offset.col % indent_width as usize != 0) as usize
|
||||||
+ editor_config.indent_guides.skip_levels as usize,
|
+ editor_config.indent_guides.skip_levels as usize,
|
||||||
indent_guide_style: text_style.patch(
|
indent_guide_style: text_style.patch(
|
||||||
theme
|
theme
|
||||||
@ -462,7 +345,7 @@ pub fn new(
|
|||||||
text_style,
|
text_style,
|
||||||
draw_indent_guides: editor_config.indent_guides.render,
|
draw_indent_guides: editor_config.indent_guides.render,
|
||||||
viewport,
|
viewport,
|
||||||
col_offset,
|
offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,9 +357,13 @@ pub fn draw_grapheme(
|
|||||||
is_virtual: bool,
|
is_virtual: bool,
|
||||||
last_indent_level: &mut usize,
|
last_indent_level: &mut usize,
|
||||||
is_in_indent_area: &mut bool,
|
is_in_indent_area: &mut bool,
|
||||||
position: Position,
|
mut position: Position,
|
||||||
) {
|
) -> usize {
|
||||||
let cut_off_start = self.col_offset.saturating_sub(position.col);
|
if position.row < self.offset.row {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
position.row -= self.offset.row;
|
||||||
|
let cut_off_start = self.offset.col.saturating_sub(position.col);
|
||||||
let is_whitespace = grapheme.is_whitespace();
|
let is_whitespace = grapheme.is_whitespace();
|
||||||
|
|
||||||
// TODO is it correct to apply the whitespace style to all unicode white spaces?
|
// TODO is it correct to apply the whitespace style to all unicode white spaces?
|
||||||
@ -508,12 +395,11 @@ pub fn draw_grapheme(
|
|||||||
Grapheme::Newline => &self.newline,
|
Grapheme::Newline => &self.newline,
|
||||||
};
|
};
|
||||||
|
|
||||||
let in_bounds = self.col_offset <= position.col
|
let in_bounds = self.column_in_bounds(position.col + width - 1);
|
||||||
&& position.col < self.viewport.width as usize + self.col_offset;
|
|
||||||
|
|
||||||
if in_bounds {
|
if in_bounds {
|
||||||
self.surface.set_string(
|
self.surface.set_string(
|
||||||
self.viewport.x + (position.col - self.col_offset) as u16,
|
self.viewport.x + (position.col - self.offset.col) as u16,
|
||||||
self.viewport.y + position.row as u16,
|
self.viewport.y + position.row as u16,
|
||||||
grapheme,
|
grapheme,
|
||||||
style,
|
style,
|
||||||
@ -533,26 +419,33 @@ pub fn draw_grapheme(
|
|||||||
*last_indent_level = position.col;
|
*last_indent_level = position.col;
|
||||||
*is_in_indent_area = false;
|
*is_in_indent_area = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_in_bounds(&self, colum: usize) -> bool {
|
||||||
|
self.offset.col <= colum && colum < self.viewport.width as usize + self.offset.col
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Overlay indentation guides ontop of a rendered line
|
/// Overlay indentation guides ontop of a rendered line
|
||||||
/// The indentation level is computed in `draw_lines`.
|
/// The indentation level is computed in `draw_lines`.
|
||||||
/// Therefore this function must always be called afterwards.
|
/// Therefore this function must always be called afterwards.
|
||||||
pub fn draw_indent_guides(&mut self, indent_level: usize, row: u16) {
|
pub fn draw_indent_guides(&mut self, indent_level: usize, mut row: u16) {
|
||||||
if !self.draw_indent_guides {
|
if !self.draw_indent_guides || self.offset.row > row as usize {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
row -= self.offset.row as u16;
|
||||||
|
|
||||||
// Don't draw indent guides outside of view
|
// Don't draw indent guides outside of view
|
||||||
let end_indent = min(
|
let end_indent = min(
|
||||||
indent_level,
|
indent_level,
|
||||||
// Add indent_width - 1 to round up, since the first visible
|
// Add indent_width - 1 to round up, since the first visible
|
||||||
// indent might be a bit after offset.col
|
// indent might be a bit after offset.col
|
||||||
self.col_offset + self.viewport.width as usize + (self.indent_width as usize - 1),
|
self.offset.col + self.viewport.width as usize + (self.indent_width as usize - 1),
|
||||||
) / self.indent_width as usize;
|
) / self.indent_width as usize;
|
||||||
|
|
||||||
for i in self.starting_indent..end_indent {
|
for i in self.starting_indent..end_indent {
|
||||||
let x = (self.viewport.x as usize + (i * self.indent_width as usize) - self.col_offset)
|
let x = (self.viewport.x as usize + (i * self.indent_width as usize) - self.offset.col)
|
||||||
as u16;
|
as u16;
|
||||||
let y = self.viewport.y + row;
|
let y = self.viewport.y + row;
|
||||||
debug_assert!(self.surface.in_bounds(x, y));
|
debug_assert!(self.surface.in_bounds(x, y));
|
||||||
@ -560,4 +453,62 @@ pub fn draw_indent_guides(&mut self, indent_level: usize, row: u16) {
|
|||||||
.set_string(x, y, &self.indent_guide_char, self.indent_guide_style);
|
.set_string(x, y, &self.indent_guide_char, self.indent_guide_style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_string(&mut self, x: u16, y: u16, string: impl AsRef<str>, style: Style) {
|
||||||
|
if (y as usize) < self.offset.row {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.surface
|
||||||
|
.set_string(x, y + self.viewport.y, string, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_stringn(
|
||||||
|
&mut self,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
string: impl AsRef<str>,
|
||||||
|
width: usize,
|
||||||
|
style: Style,
|
||||||
|
) {
|
||||||
|
if (y as usize) < self.offset.row {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.surface
|
||||||
|
.set_stringn(x, y + self.viewport.y, string, width, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style of an area **within the text viewport* this accounts
|
||||||
|
/// both for the renderers vertical offset and its viewport
|
||||||
|
pub fn set_style(&mut self, mut area: Rect, style: Style) {
|
||||||
|
area = area.clip_top(self.offset.row as u16);
|
||||||
|
area.y += self.viewport.y;
|
||||||
|
self.surface.set_style(area, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the style of an area **within the text viewport* this accounts
|
||||||
|
/// both for the renderers vertical offset and its viewport
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn set_string_truncated(
|
||||||
|
&mut self,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
string: &str,
|
||||||
|
width: usize,
|
||||||
|
style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style
|
||||||
|
ellipsis: bool,
|
||||||
|
truncate_start: bool,
|
||||||
|
) -> (u16, u16) {
|
||||||
|
if (y as usize) < self.offset.row {
|
||||||
|
return (x, y);
|
||||||
|
}
|
||||||
|
self.surface.set_string_truncated(
|
||||||
|
x,
|
||||||
|
y + self.viewport.y,
|
||||||
|
string,
|
||||||
|
width,
|
||||||
|
style,
|
||||||
|
ellipsis,
|
||||||
|
truncate_start,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
key,
|
key,
|
||||||
keymap::{KeymapResult, Keymaps},
|
keymap::{KeymapResult, Keymaps},
|
||||||
ui::{
|
ui::{
|
||||||
document::{render_document, LinePos, TextRenderer, TranslatedPosition},
|
document::{render_document, LinePos, TextRenderer},
|
||||||
Completion, ProgressSpinners,
|
statusline,
|
||||||
|
text_decorations::{self, Decoration, DecorationManager, InlineDiagnostics},
|
||||||
|
Completion, CompletionItem, ProgressSpinners,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,9 +33,6 @@
|
|||||||
|
|
||||||
use tui::{buffer::Buffer as Surface, text::Span};
|
use tui::{buffer::Buffer as Surface, text::Span};
|
||||||
|
|
||||||
use super::document::LineDecoration;
|
|
||||||
use super::{completion::CompletionItem, statusline};
|
|
||||||
|
|
||||||
pub struct EditorView {
|
pub struct EditorView {
|
||||||
pub keymaps: Keymaps,
|
pub keymaps: Keymaps,
|
||||||
on_next_key: Option<OnKeyCallback>,
|
on_next_key: Option<OnKeyCallback>,
|
||||||
@ -94,11 +93,10 @@ pub fn render_view(
|
|||||||
let config = editor.config();
|
let config = editor.config();
|
||||||
|
|
||||||
let text_annotations = view.text_annotations(doc, Some(theme));
|
let text_annotations = view.text_annotations(doc, Some(theme));
|
||||||
let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
|
let mut decorations = DecorationManager::default();
|
||||||
let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
|
|
||||||
|
|
||||||
if is_focused && config.cursorline {
|
if is_focused && config.cursorline {
|
||||||
line_decorations.push(Self::cursorline_decorator(doc, view, theme))
|
decorations.add_decoration(Self::cursorline(doc, view, theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_focused && config.cursorcolumn {
|
if is_focused && config.cursorcolumn {
|
||||||
@ -113,13 +111,10 @@ pub fn render_view(
|
|||||||
if pos.doc_line != dap_line {
|
if pos.doc_line != dap_line {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderer.surface.set_style(
|
renderer.set_style(Rect::new(inner.x, pos.visual_line, inner.width, 1), style);
|
||||||
Rect::new(inner.x, inner.y + pos.visual_line, inner.width, 1),
|
|
||||||
style,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
line_decorations.push(Box::new(line_decoration));
|
decorations.add_decoration(line_decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
let syntax_highlights =
|
let syntax_highlights =
|
||||||
@ -176,22 +171,20 @@ pub fn render_view(
|
|||||||
view.area,
|
view.area,
|
||||||
theme,
|
theme,
|
||||||
is_focused & self.terminal_focused,
|
is_focused & self.terminal_focused,
|
||||||
&mut line_decorations,
|
&mut decorations,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_focused {
|
let primary_cursor = doc
|
||||||
let cursor = doc
|
|
||||||
.selection(view.id)
|
.selection(view.id)
|
||||||
.primary()
|
.primary()
|
||||||
.cursor(doc.text().slice(..));
|
.cursor(doc.text().slice(..));
|
||||||
// set the cursor_cache to out of view in case the position is not found
|
if is_focused {
|
||||||
editor.cursor_cache.set(Some(None));
|
decorations.add_decoration(text_decorations::Cursor {
|
||||||
let update_cursor_cache =
|
cache: &editor.cursor_cache,
|
||||||
|_: &mut TextRenderer, pos| editor.cursor_cache.set(Some(Some(pos)));
|
primary_cursor,
|
||||||
translated_positions.push((cursor, Box::new(update_cursor_cache)));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render_document(
|
render_document(
|
||||||
surface,
|
surface,
|
||||||
inner,
|
inner,
|
||||||
@ -201,8 +194,7 @@ pub fn render_view(
|
|||||||
syntax_highlights,
|
syntax_highlights,
|
||||||
overlay_highlights,
|
overlay_highlights,
|
||||||
theme,
|
theme,
|
||||||
&mut line_decorations,
|
decorations,
|
||||||
&mut translated_positions,
|
|
||||||
);
|
);
|
||||||
Self::render_rulers(editor, doc, view, inner, surface, theme);
|
Self::render_rulers(editor, doc, view, inner, surface, theme);
|
||||||
|
|
||||||
@ -637,7 +629,7 @@ pub fn render_gutter<'d>(
|
|||||||
viewport: Rect,
|
viewport: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
line_decorations: &mut Vec<Box<(dyn LineDecoration + 'd)>>,
|
decoration_manager: &mut DecorationManager<'d>,
|
||||||
) {
|
) {
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
let cursors: Rc<[_]> = doc
|
let cursors: Rc<[_]> = doc
|
||||||
@ -663,7 +655,7 @@ pub fn render_gutter<'d>(
|
|||||||
// TODO handle softwrap in gutters
|
// TODO handle softwrap in gutters
|
||||||
let selected = cursors.contains(&pos.doc_line);
|
let selected = cursors.contains(&pos.doc_line);
|
||||||
let x = viewport.x + offset;
|
let x = viewport.x + offset;
|
||||||
let y = viewport.y + pos.visual_line;
|
let y = pos.visual_line;
|
||||||
|
|
||||||
let gutter_style = match (selected, pos.first_visual_line) {
|
let gutter_style = match (selected, pos.first_visual_line) {
|
||||||
(false, true) => gutter_style,
|
(false, true) => gutter_style,
|
||||||
@ -675,11 +667,9 @@ pub fn render_gutter<'d>(
|
|||||||
if let Some(style) =
|
if let Some(style) =
|
||||||
gutter(pos.doc_line, selected, pos.first_visual_line, &mut text)
|
gutter(pos.doc_line, selected, pos.first_visual_line, &mut text)
|
||||||
{
|
{
|
||||||
renderer
|
renderer.set_stringn(x, y, &text, width, gutter_style.patch(style));
|
||||||
.surface
|
|
||||||
.set_stringn(x, y, &text, width, gutter_style.patch(style));
|
|
||||||
} else {
|
} else {
|
||||||
renderer.surface.set_style(
|
renderer.set_style(
|
||||||
Rect {
|
Rect {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
@ -691,7 +681,7 @@ pub fn render_gutter<'d>(
|
|||||||
}
|
}
|
||||||
text.clear();
|
text.clear();
|
||||||
};
|
};
|
||||||
line_decorations.push(Box::new(gutter_decoration));
|
decoration_manager.add_decoration(gutter_decoration);
|
||||||
|
|
||||||
offset += width as u16;
|
offset += width as u16;
|
||||||
}
|
}
|
||||||
@ -761,11 +751,7 @@ pub fn render_diagnostics(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the highlighting on the lines where a cursor is active
|
/// Apply the highlighting on the lines where a cursor is active
|
||||||
pub fn cursorline_decorator(
|
pub fn cursorline(doc: &Document, view: &View, theme: &Theme) -> impl Decoration {
|
||||||
doc: &Document,
|
|
||||||
view: &View,
|
|
||||||
theme: &Theme,
|
|
||||||
) -> Box<dyn LineDecoration> {
|
|
||||||
let text = doc.text().slice(..);
|
let text = doc.text().slice(..);
|
||||||
// TODO only highlight the visual line that contains the cursor instead of the full visual line
|
// TODO only highlight the visual line that contains the cursor instead of the full visual line
|
||||||
let primary_line = doc.selection(view.id).primary().cursor_line(text);
|
let primary_line = doc.selection(view.id).primary().cursor_line(text);
|
||||||
@ -786,16 +772,14 @@ pub fn cursorline_decorator(
|
|||||||
let secondary_style = theme.get("ui.cursorline.secondary");
|
let secondary_style = theme.get("ui.cursorline.secondary");
|
||||||
let viewport = view.area;
|
let viewport = view.area;
|
||||||
|
|
||||||
let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
|
move |renderer: &mut TextRenderer, pos: LinePos| {
|
||||||
let area = Rect::new(viewport.x, viewport.y + pos.visual_line, viewport.width, 1);
|
let area = Rect::new(viewport.x, pos.visual_line, viewport.width, 1);
|
||||||
if primary_line == pos.doc_line {
|
if primary_line == pos.doc_line {
|
||||||
renderer.surface.set_style(area, primary_style);
|
renderer.set_style(area, primary_style);
|
||||||
} else if secondary_lines.binary_search(&pos.doc_line).is_ok() {
|
} else if secondary_lines.binary_search(&pos.doc_line).is_ok() {
|
||||||
renderer.surface.set_style(area, secondary_style);
|
renderer.set_style(area, secondary_style);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Box::new(line_decoration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the highlighting on the columns where a cursor is active
|
/// Apply the highlighting on the columns where a cursor is active
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
mod spinner;
|
mod spinner;
|
||||||
mod statusline;
|
mod statusline;
|
||||||
mod text;
|
mod text;
|
||||||
|
mod text_decorations;
|
||||||
|
|
||||||
use crate::compositor::Compositor;
|
use crate::compositor::Compositor;
|
||||||
use crate::filter_picker_entry;
|
use crate::filter_picker_entry;
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
ctrl, key, shift,
|
ctrl, key, shift,
|
||||||
ui::{
|
ui::{
|
||||||
self,
|
self,
|
||||||
document::{render_document, LineDecoration, LinePos, TextRenderer},
|
document::{render_document, LinePos, TextRenderer},
|
||||||
picker::query::PickerQuery,
|
picker::query::PickerQuery,
|
||||||
|
text_decorations::DecorationManager,
|
||||||
EditorView,
|
EditorView,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -895,7 +896,7 @@ fn render_preview(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context
|
|||||||
}
|
}
|
||||||
overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
|
overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
|
||||||
}
|
}
|
||||||
let mut decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
|
let mut decorations = DecorationManager::default();
|
||||||
|
|
||||||
if let Some((start, end)) = range {
|
if let Some((start, end)) = range {
|
||||||
let style = cx
|
let style = cx
|
||||||
@ -907,14 +908,14 @@ fn render_preview(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context
|
|||||||
if (start..=end).contains(&pos.doc_line) {
|
if (start..=end).contains(&pos.doc_line) {
|
||||||
let area = Rect::new(
|
let area = Rect::new(
|
||||||
renderer.viewport.x,
|
renderer.viewport.x,
|
||||||
renderer.viewport.y + pos.visual_line,
|
pos.visual_line,
|
||||||
renderer.viewport.width,
|
renderer.viewport.width,
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
renderer.surface.set_style(area, style)
|
renderer.set_style(area, style)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
decorations.push(Box::new(draw_highlight))
|
decorations.add_decoration(draw_highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
render_document(
|
render_document(
|
||||||
@ -927,8 +928,7 @@ fn render_preview(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context
|
|||||||
syntax_highlights,
|
syntax_highlights,
|
||||||
overlay_highlights,
|
overlay_highlights,
|
||||||
&cx.editor.theme,
|
&cx.editor.theme,
|
||||||
&mut decorations,
|
decorations,
|
||||||
&mut [],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
175
helix-term/src/ui/text_decorations.rs
Normal file
175
helix-term/src/ui/text_decorations.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use helix_core::doc_formatter::FormattedGrapheme;
|
||||||
|
use helix_core::Position;
|
||||||
|
use helix_view::editor::CursorCache;
|
||||||
|
|
||||||
|
use crate::ui::document::{LinePos, TextRenderer};
|
||||||
|
|
||||||
|
pub use diagnostics::InlineDiagnostics;
|
||||||
|
|
||||||
|
mod diagnostics;
|
||||||
|
|
||||||
|
/// Decorations are the primary mechanism for extending the text rendering.
|
||||||
|
///
|
||||||
|
/// Any on-screen element which is anchored to the rendered text in some form
|
||||||
|
/// should be implemented using this trait. Translating char positions to
|
||||||
|
/// on-screen positions can be expensive and should not be done manually in the
|
||||||
|
/// ui loop. Instead such translations are automatically performed on the fly
|
||||||
|
/// while the text is being rendered. The results are provided to this trait by
|
||||||
|
/// the rendering infrastructure.
|
||||||
|
///
|
||||||
|
/// To reserve space for virtual text lines (which is then filled by this trait) emit appropriate
|
||||||
|
/// [`LineAnnotation`](helix_core::text_annotations::LineAnnotation)s in [`helix_view::View::text_annotations`]
|
||||||
|
pub trait Decoration {
|
||||||
|
/// Called **before** a **visual** line is rendered. A visual line does not
|
||||||
|
/// necessarily correspond to a single line in a document as soft wrapping can
|
||||||
|
/// spread a single document line across multiple visual lines.
|
||||||
|
///
|
||||||
|
/// This function is called before text is rendered as any decorations should
|
||||||
|
/// never overlap the document text. That means that setting the forground color
|
||||||
|
/// here is (essentially) useless as the text color is overwritten by the
|
||||||
|
/// rendered text. This _of course_ doesn't apply when rendering inside virtual lines
|
||||||
|
/// below the line reserved by `LineAnnotation`s as no text will be rendered here.
|
||||||
|
fn decorate_line(&mut self, _renderer: &mut TextRenderer, _pos: LinePos) {}
|
||||||
|
|
||||||
|
/// Called **after** a **visual** line is rendered. A visual line does not
|
||||||
|
/// necessarily correspond to a single line in a document as soft wrapping can
|
||||||
|
/// spread a single document line across multiple visual lines.
|
||||||
|
///
|
||||||
|
/// This function is called after text is rendered so that decorations can collect
|
||||||
|
/// horizontal positions on the line (see [`Decoration::decorate_grapheme`]) first and
|
||||||
|
/// use those positions` while rendering
|
||||||
|
/// virtual text.
|
||||||
|
/// That means that setting the forground color
|
||||||
|
/// here is (essentially) useless as the text color is overwritten by the
|
||||||
|
/// rendered text. This -ofcourse- doesn't apply when rendering inside virtual lines
|
||||||
|
/// below the line reserved by `LineAnnotation`s. e as no text will be rendered here.
|
||||||
|
/// **Note**: To avoid overlapping decorations in the virtual lines, each decoration
|
||||||
|
/// must return the number of virtual text lines it has taken up. Each `Decoration` recieves
|
||||||
|
/// an offset `virt_off` based on these return values where it can render virtual text:
|
||||||
|
///
|
||||||
|
/// That means that a `render_line` implementation that returns `X` can render virtual text
|
||||||
|
/// in the following area:
|
||||||
|
/// ``` no-compile
|
||||||
|
/// let start = inner.y + pos.virtual_line + virt_off;
|
||||||
|
/// start .. start + X
|
||||||
|
/// ````
|
||||||
|
fn render_virt_lines(
|
||||||
|
&mut self,
|
||||||
|
_renderer: &mut TextRenderer,
|
||||||
|
_pos: LinePos,
|
||||||
|
_virt_off: Position,
|
||||||
|
) -> Position {
|
||||||
|
Position::new(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_pos(&mut self, _pos: usize) -> usize {
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_concealed_anchor(&mut self, conceal_end_char_idx: usize) -> usize {
|
||||||
|
self.reset_pos(conceal_end_char_idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is called **before** the grapheme at `char_idx` is rendered.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The char idx of the next grapheme that this function should be called for
|
||||||
|
fn decorate_grapheme(
|
||||||
|
&mut self,
|
||||||
|
_renderer: &mut TextRenderer,
|
||||||
|
_grapheme: &FormattedGrapheme,
|
||||||
|
) -> usize {
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnMut(&mut TextRenderer, LinePos)> Decoration for F {
|
||||||
|
fn decorate_line(&mut self, renderer: &mut TextRenderer, pos: LinePos) {
|
||||||
|
self(renderer, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DecorationManager<'a> {
|
||||||
|
decorations: Vec<(Box<dyn Decoration + 'a>, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DecorationManager<'a> {
|
||||||
|
pub fn add_decoration(&mut self, decoration: impl Decoration + 'a) {
|
||||||
|
self.decorations.push((Box::new(decoration), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_for_rendering(&mut self, first_visible_char: usize) {
|
||||||
|
for (decoration, next_position) in &mut self.decorations {
|
||||||
|
*next_position = decoration.reset_pos(first_visible_char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decorate_grapheme(&mut self, renderer: &mut TextRenderer, grapheme: &FormattedGrapheme) {
|
||||||
|
for (decoration, hook_char_idx) in &mut self.decorations {
|
||||||
|
loop {
|
||||||
|
match (*hook_char_idx).cmp(&grapheme.char_idx) {
|
||||||
|
// this grapheme has been concealed or we are at the first grapheme
|
||||||
|
Ordering::Less => {
|
||||||
|
*hook_char_idx = decoration.skip_concealed_anchor(grapheme.char_idx)
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
*hook_char_idx = decoration.decorate_grapheme(renderer, grapheme)
|
||||||
|
}
|
||||||
|
Ordering::Greater => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decorate_line(&mut self, renderer: &mut TextRenderer, pos: LinePos) {
|
||||||
|
for (decoration, _) in &mut self.decorations {
|
||||||
|
decoration.decorate_line(renderer, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_virtual_lines(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut TextRenderer,
|
||||||
|
pos: LinePos,
|
||||||
|
line_width: usize,
|
||||||
|
) {
|
||||||
|
let mut virt_off = Position::new(1, line_width); // start at 1 so the line is never overwritten
|
||||||
|
for (decoration, _) in &mut self.decorations {
|
||||||
|
virt_off += decoration.render_virt_lines(renderer, pos, virt_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cursor rendering is done externally so all the cursor decoration
|
||||||
|
/// does is save the position of primary cursor
|
||||||
|
pub struct Cursor<'a> {
|
||||||
|
pub cache: &'a CursorCache,
|
||||||
|
pub primary_cursor: usize,
|
||||||
|
}
|
||||||
|
impl Decoration for Cursor<'_> {
|
||||||
|
fn reset_pos(&mut self, pos: usize) -> usize {
|
||||||
|
if pos <= self.primary_cursor {
|
||||||
|
self.primary_cursor
|
||||||
|
} else {
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decorate_grapheme(
|
||||||
|
&mut self,
|
||||||
|
renderer: &mut TextRenderer,
|
||||||
|
grapheme: &FormattedGrapheme,
|
||||||
|
) -> usize {
|
||||||
|
if renderer.column_in_bounds(grapheme.visual_pos.col)
|
||||||
|
&& renderer.offset.row < grapheme.visual_pos.row
|
||||||
|
{
|
||||||
|
let position = grapheme.visual_pos - renderer.offset;
|
||||||
|
self.cache.set(Some(position));
|
||||||
|
}
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
}
|
@ -1070,10 +1070,10 @@ pub struct Editor {
|
|||||||
/// This cache is only a performance optimization to
|
/// This cache is only a performance optimization to
|
||||||
/// avoid calculating the cursor position multiple
|
/// avoid calculating the cursor position multiple
|
||||||
/// times during rendering and should not be set by other functions.
|
/// times during rendering and should not be set by other functions.
|
||||||
pub cursor_cache: Cell<Option<Option<Position>>>,
|
|
||||||
pub handlers: Handlers,
|
pub handlers: Handlers,
|
||||||
|
|
||||||
pub mouse_down_range: Option<Range>,
|
pub mouse_down_range: Option<Range>,
|
||||||
|
pub cursor_cache: CursorCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Motion = Box<dyn Fn(&mut Editor)>;
|
pub type Motion = Box<dyn Fn(&mut Editor)>;
|
||||||
@ -1188,9 +1188,9 @@ pub fn new(
|
|||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
config_events: unbounded_channel(),
|
config_events: unbounded_channel(),
|
||||||
needs_redraw: false,
|
needs_redraw: false,
|
||||||
cursor_cache: Cell::new(None),
|
|
||||||
handlers,
|
handlers,
|
||||||
mouse_down_range: None,
|
mouse_down_range: None,
|
||||||
|
cursor_cache: CursorCache::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1985,15 +1985,7 @@ pub fn doc_diagnostics_with_filter<'a>(
|
|||||||
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
|
||||||
let config = self.config();
|
let config = self.config();
|
||||||
let (view, doc) = current_ref!(self);
|
let (view, doc) = current_ref!(self);
|
||||||
let cursor = doc
|
if let Some(mut pos) = self.cursor_cache.get(view, doc) {
|
||||||
.selection(view.id)
|
|
||||||
.primary()
|
|
||||||
.cursor(doc.text().slice(..));
|
|
||||||
let pos = self
|
|
||||||
.cursor_cache
|
|
||||||
.get()
|
|
||||||
.unwrap_or_else(|| view.screen_coords_at_pos(doc, doc.text().slice(..), cursor));
|
|
||||||
if let Some(mut pos) = pos {
|
|
||||||
let inner = view.inner_area(doc);
|
let inner = view.inner_area(doc);
|
||||||
pos.col += inner.x as usize;
|
pos.col += inner.x as usize;
|
||||||
pos.row += inner.y as usize;
|
pos.row += inner.y as usize;
|
||||||
@ -2188,3 +2180,28 @@ fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: us
|
|||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CursorCache(Cell<Option<Option<Position>>>);
|
||||||
|
|
||||||
|
impl CursorCache {
|
||||||
|
pub fn get(&self, view: &View, doc: &Document) -> Option<Position> {
|
||||||
|
if let Some(pos) = self.0.get() {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let cursor = doc.selection(view.id).primary().cursor(text);
|
||||||
|
let res = view.screen_coords_at_pos(doc, text, cursor);
|
||||||
|
self.set(res);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, cursor_pos: Option<Position>) {
|
||||||
|
self.0.set(Some(cursor_pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self) {
|
||||||
|
self.0.set(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user