mirror of https://github.com/helix-editor/helix
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.pull/6417/head
parent
9a93240d27
commit
2c0506aa96
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue