|
|
@ -6,9 +6,11 @@ use helix_core::str_utils::char_to_byte_idx;
|
|
|
|
use helix_core::syntax::Highlight;
|
|
|
|
use helix_core::syntax::Highlight;
|
|
|
|
use helix_core::syntax::HighlightEvent;
|
|
|
|
use helix_core::syntax::HighlightEvent;
|
|
|
|
use helix_core::text_annotations::TextAnnotations;
|
|
|
|
use helix_core::text_annotations::TextAnnotations;
|
|
|
|
use helix_core::{visual_offset_from_block, Position, RopeSlice};
|
|
|
|
use helix_core::{visual_offset_from_block, Position, RopeSlice, Selection};
|
|
|
|
use helix_stdx::rope::RopeSliceExt;
|
|
|
|
use helix_stdx::rope::RopeSliceExt;
|
|
|
|
use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue};
|
|
|
|
use helix_view::editor::{
|
|
|
|
|
|
|
|
WhitespaceCharacters, WhitespaceConfig, WhitespaceRender, WhitespaceRenderValue,
|
|
|
|
|
|
|
|
};
|
|
|
|
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;
|
|
|
@ -86,6 +88,7 @@ pub fn render_document(
|
|
|
|
surface: &mut Surface,
|
|
|
|
surface: &mut Surface,
|
|
|
|
viewport: Rect,
|
|
|
|
viewport: Rect,
|
|
|
|
doc: &Document,
|
|
|
|
doc: &Document,
|
|
|
|
|
|
|
|
selection: Option<&Selection>,
|
|
|
|
offset: ViewPosition,
|
|
|
|
offset: ViewPosition,
|
|
|
|
doc_annotations: &TextAnnotations,
|
|
|
|
doc_annotations: &TextAnnotations,
|
|
|
|
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
|
|
|
syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
|
|
|
@ -103,6 +106,7 @@ pub fn render_document(
|
|
|
|
render_text(
|
|
|
|
render_text(
|
|
|
|
&mut renderer,
|
|
|
|
&mut renderer,
|
|
|
|
doc.text().slice(..),
|
|
|
|
doc.text().slice(..),
|
|
|
|
|
|
|
|
selection,
|
|
|
|
offset.anchor,
|
|
|
|
offset.anchor,
|
|
|
|
&doc.text_format(viewport.width, Some(theme)),
|
|
|
|
&doc.text_format(viewport.width, Some(theme)),
|
|
|
|
doc_annotations,
|
|
|
|
doc_annotations,
|
|
|
@ -117,6 +121,7 @@ pub fn render_document(
|
|
|
|
pub fn render_text(
|
|
|
|
pub fn render_text(
|
|
|
|
renderer: &mut TextRenderer,
|
|
|
|
renderer: &mut TextRenderer,
|
|
|
|
text: RopeSlice<'_>,
|
|
|
|
text: RopeSlice<'_>,
|
|
|
|
|
|
|
|
selection: Option<&Selection>,
|
|
|
|
anchor: usize,
|
|
|
|
anchor: usize,
|
|
|
|
text_fmt: &TextFormat,
|
|
|
|
text_fmt: &TextFormat,
|
|
|
|
text_annotations: &TextAnnotations,
|
|
|
|
text_annotations: &TextAnnotations,
|
|
|
@ -234,10 +239,16 @@ pub fn render_text(
|
|
|
|
decorations.decorate_grapheme(renderer, &grapheme);
|
|
|
|
decorations.decorate_grapheme(renderer, &grapheme);
|
|
|
|
|
|
|
|
|
|
|
|
let virt = grapheme.is_virtual();
|
|
|
|
let virt = grapheme.is_virtual();
|
|
|
|
|
|
|
|
let is_selected = selection.is_some_and(|selection| {
|
|
|
|
|
|
|
|
selection
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.any(|range| range.contains(grapheme.char_idx))
|
|
|
|
|
|
|
|
});
|
|
|
|
let grapheme_width = renderer.draw_grapheme(
|
|
|
|
let grapheme_width = renderer.draw_grapheme(
|
|
|
|
grapheme.raw,
|
|
|
|
grapheme.raw,
|
|
|
|
grapheme_style,
|
|
|
|
grapheme_style,
|
|
|
|
virt,
|
|
|
|
virt,
|
|
|
|
|
|
|
|
is_selected,
|
|
|
|
&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,
|
|
|
@ -252,16 +263,11 @@ pub fn render_text(
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TextRenderer<'a> {
|
|
|
|
pub struct TextRenderer<'a> {
|
|
|
|
surface: &'a mut Surface,
|
|
|
|
surface: &'a mut Surface,
|
|
|
|
|
|
|
|
whitespace_entries: WhitespaceEntries,
|
|
|
|
pub text_style: Style,
|
|
|
|
pub text_style: Style,
|
|
|
|
pub whitespace_style: Style,
|
|
|
|
pub whitespace_style: Style,
|
|
|
|
pub indent_guide_char: String,
|
|
|
|
pub indent_guide_char: String,
|
|
|
|
pub indent_guide_style: Style,
|
|
|
|
pub indent_guide_style: Style,
|
|
|
|
pub newline: String,
|
|
|
|
|
|
|
|
pub nbsp: String,
|
|
|
|
|
|
|
|
pub nnbsp: String,
|
|
|
|
|
|
|
|
pub space: String,
|
|
|
|
|
|
|
|
pub tab: String,
|
|
|
|
|
|
|
|
pub virtual_tab: String,
|
|
|
|
|
|
|
|
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,
|
|
|
@ -289,36 +295,7 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
} = &editor_config.whitespace;
|
|
|
|
} = &editor_config.whitespace;
|
|
|
|
|
|
|
|
|
|
|
|
let tab_width = doc.tab_width();
|
|
|
|
let tab_width = doc.tab_width();
|
|
|
|
let tab = if ws_render.tab() == WhitespaceRenderValue::All {
|
|
|
|
let whitespace_entries = WhitespaceEntries::new(ws_render, ws_chars, tab_width);
|
|
|
|
std::iter::once(ws_chars.tab)
|
|
|
|
|
|
|
|
.chain(std::iter::repeat(ws_chars.tabpad).take(tab_width - 1))
|
|
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
" ".repeat(tab_width)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let virtual_tab = " ".repeat(tab_width);
|
|
|
|
|
|
|
|
let newline = if ws_render.newline() == WhitespaceRenderValue::All {
|
|
|
|
|
|
|
|
ws_chars.newline.into()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
" ".to_owned()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let space = if ws_render.space() == WhitespaceRenderValue::All {
|
|
|
|
|
|
|
|
ws_chars.space.into()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
" ".to_owned()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let nbsp = if ws_render.nbsp() == WhitespaceRenderValue::All {
|
|
|
|
|
|
|
|
ws_chars.nbsp.into()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
" ".to_owned()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let nnbsp = if ws_render.nnbsp() == WhitespaceRenderValue::All {
|
|
|
|
|
|
|
|
ws_chars.nnbsp.into()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
" ".to_owned()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let text_style = theme.get("ui.text");
|
|
|
|
let text_style = theme.get("ui.text");
|
|
|
|
|
|
|
|
|
|
|
|
let indent_width = doc.indent_style.indent_width(tab_width) as u16;
|
|
|
|
let indent_width = doc.indent_style.indent_width(tab_width) as u16;
|
|
|
@ -326,12 +303,7 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
TextRenderer {
|
|
|
|
TextRenderer {
|
|
|
|
surface,
|
|
|
|
surface,
|
|
|
|
indent_guide_char: editor_config.indent_guides.character.into(),
|
|
|
|
indent_guide_char: editor_config.indent_guides.character.into(),
|
|
|
|
newline,
|
|
|
|
whitespace_entries,
|
|
|
|
nbsp,
|
|
|
|
|
|
|
|
nnbsp,
|
|
|
|
|
|
|
|
space,
|
|
|
|
|
|
|
|
tab,
|
|
|
|
|
|
|
|
virtual_tab,
|
|
|
|
|
|
|
|
whitespace_style: theme.get("ui.virtual.whitespace"),
|
|
|
|
whitespace_style: theme.get("ui.virtual.whitespace"),
|
|
|
|
indent_width,
|
|
|
|
indent_width,
|
|
|
|
starting_indent: offset.col / indent_width as usize
|
|
|
|
starting_indent: offset.col / indent_width as usize
|
|
|
@ -368,10 +340,11 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
style = style.patch(self.whitespace_style);
|
|
|
|
style = style.patch(self.whitespace_style);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let virtual_tab = &self.whitespace_entries.tab.render(true, false);
|
|
|
|
let grapheme = match grapheme {
|
|
|
|
let grapheme = match grapheme {
|
|
|
|
Grapheme::Tab { width } => {
|
|
|
|
Grapheme::Tab { width } => {
|
|
|
|
let grapheme_tab_width = char_to_byte_idx(&self.virtual_tab, width);
|
|
|
|
let grapheme_tab_width = char_to_byte_idx(virtual_tab, width);
|
|
|
|
&self.virtual_tab[..grapheme_tab_width]
|
|
|
|
&virtual_tab[..grapheme_tab_width]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Grapheme::Other { ref g } if g == "\u{00A0}" => " ",
|
|
|
|
Grapheme::Other { ref g } if g == "\u{00A0}" => " ",
|
|
|
|
Grapheme::Other { ref g } => g,
|
|
|
|
Grapheme::Other { ref g } => g,
|
|
|
@ -393,6 +366,7 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
grapheme: Grapheme,
|
|
|
|
grapheme: Grapheme,
|
|
|
|
grapheme_style: GraphemeStyle,
|
|
|
|
grapheme_style: GraphemeStyle,
|
|
|
|
is_virtual: bool,
|
|
|
|
is_virtual: bool,
|
|
|
|
|
|
|
|
is_selected: bool,
|
|
|
|
last_indent_level: &mut usize,
|
|
|
|
last_indent_level: &mut usize,
|
|
|
|
is_in_indent_area: &mut bool,
|
|
|
|
is_in_indent_area: &mut bool,
|
|
|
|
mut position: Position,
|
|
|
|
mut position: Position,
|
|
|
@ -412,14 +386,14 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
style = style.patch(grapheme_style.overlay_style);
|
|
|
|
style = style.patch(grapheme_style.overlay_style);
|
|
|
|
|
|
|
|
|
|
|
|
let width = grapheme.width();
|
|
|
|
let width = grapheme.width();
|
|
|
|
let space = if is_virtual { " " } else { &self.space };
|
|
|
|
|
|
|
|
let nbsp = if is_virtual { " " } else { &self.nbsp };
|
|
|
|
let ws = &self.whitespace_entries;
|
|
|
|
let nnbsp = if is_virtual { " " } else { &self.nnbsp };
|
|
|
|
let tab = &ws.tab.render(is_virtual, is_selected);
|
|
|
|
let tab = if is_virtual {
|
|
|
|
let space = &ws.space.render(is_virtual, is_selected);
|
|
|
|
&self.virtual_tab
|
|
|
|
let nbsp = &ws.nbsp.render(is_virtual, is_selected);
|
|
|
|
} else {
|
|
|
|
let nnbsp = &ws.nnbsp.render(is_virtual, is_selected);
|
|
|
|
&self.tab
|
|
|
|
let newline = &ws.newline.render(false, is_selected);
|
|
|
|
};
|
|
|
|
|
|
|
|
let grapheme = match grapheme {
|
|
|
|
let grapheme = match grapheme {
|
|
|
|
Grapheme::Tab { width } => {
|
|
|
|
Grapheme::Tab { width } => {
|
|
|
|
let grapheme_tab_width = char_to_byte_idx(tab, width);
|
|
|
|
let grapheme_tab_width = char_to_byte_idx(tab, width);
|
|
|
@ -430,7 +404,7 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp,
|
|
|
|
Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp,
|
|
|
|
Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp,
|
|
|
|
Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp,
|
|
|
|
Grapheme::Other { ref g } => g,
|
|
|
|
Grapheme::Other { ref g } => g,
|
|
|
|
Grapheme::Newline => &self.newline,
|
|
|
|
Grapheme::Newline => newline,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let in_bounds = self.column_in_bounds(position.col + width - 1);
|
|
|
|
let in_bounds = self.column_in_bounds(position.col + width - 1);
|
|
|
@ -550,3 +524,99 @@ impl<'a> TextRenderer<'a> {
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
struct WhitespacePadding {
|
|
|
|
|
|
|
|
grapheme_width: usize,
|
|
|
|
|
|
|
|
padding_character: char,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
struct Whitespace {
|
|
|
|
|
|
|
|
render_value: WhitespaceRenderValue,
|
|
|
|
|
|
|
|
character: char,
|
|
|
|
|
|
|
|
padding: Option<WhitespacePadding>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Whitespace {
|
|
|
|
|
|
|
|
fn render_hidden(&self) -> String {
|
|
|
|
|
|
|
|
let target_width = self.padding.as_ref().map(|p| p.grapheme_width).unwrap_or(1);
|
|
|
|
|
|
|
|
" ".repeat(target_width)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render_visible(&self) -> String {
|
|
|
|
|
|
|
|
match self.padding {
|
|
|
|
|
|
|
|
Some(WhitespacePadding {
|
|
|
|
|
|
|
|
grapheme_width,
|
|
|
|
|
|
|
|
padding_character,
|
|
|
|
|
|
|
|
}) => std::iter::once(self.character)
|
|
|
|
|
|
|
|
.chain(std::iter::repeat(padding_character).take(grapheme_width - 1))
|
|
|
|
|
|
|
|
.collect(),
|
|
|
|
|
|
|
|
None => self.character.to_string(),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_visible(&self, is_virtual: bool, is_selected: bool) -> bool {
|
|
|
|
|
|
|
|
if is_virtual {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
match self.render_value {
|
|
|
|
|
|
|
|
WhitespaceRenderValue::All => true,
|
|
|
|
|
|
|
|
WhitespaceRenderValue::Selection => is_selected,
|
|
|
|
|
|
|
|
WhitespaceRenderValue::None => false,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render(&self, is_virtual: bool, is_selected: bool) -> String {
|
|
|
|
|
|
|
|
if self.is_visible(is_virtual, is_selected) {
|
|
|
|
|
|
|
|
self.render_visible()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
self.render_hidden()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
struct WhitespaceEntries {
|
|
|
|
|
|
|
|
space: Whitespace,
|
|
|
|
|
|
|
|
nbsp: Whitespace,
|
|
|
|
|
|
|
|
nnbsp: Whitespace,
|
|
|
|
|
|
|
|
tab: Whitespace,
|
|
|
|
|
|
|
|
newline: Whitespace,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl WhitespaceEntries {
|
|
|
|
|
|
|
|
fn new(
|
|
|
|
|
|
|
|
whitespace_render: &WhitespaceRender,
|
|
|
|
|
|
|
|
whitespace_characters: &WhitespaceCharacters,
|
|
|
|
|
|
|
|
tab_width: usize,
|
|
|
|
|
|
|
|
) -> Self {
|
|
|
|
|
|
|
|
WhitespaceEntries {
|
|
|
|
|
|
|
|
space: Whitespace {
|
|
|
|
|
|
|
|
render_value: whitespace_render.space(),
|
|
|
|
|
|
|
|
character: whitespace_characters.space,
|
|
|
|
|
|
|
|
padding: None,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
nbsp: Whitespace {
|
|
|
|
|
|
|
|
render_value: whitespace_render.nbsp(),
|
|
|
|
|
|
|
|
character: whitespace_characters.nbsp,
|
|
|
|
|
|
|
|
padding: None,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
nnbsp: Whitespace {
|
|
|
|
|
|
|
|
render_value: whitespace_render.nnbsp(),
|
|
|
|
|
|
|
|
character: whitespace_characters.nnbsp,
|
|
|
|
|
|
|
|
padding: None,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
tab: Whitespace {
|
|
|
|
|
|
|
|
render_value: whitespace_render.tab(),
|
|
|
|
|
|
|
|
character: whitespace_characters.tab,
|
|
|
|
|
|
|
|
padding: Some(WhitespacePadding {
|
|
|
|
|
|
|
|
grapheme_width: tab_width,
|
|
|
|
|
|
|
|
padding_character: whitespace_characters.tabpad,
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
newline: Whitespace {
|
|
|
|
|
|
|
|
render_value: whitespace_render.newline(),
|
|
|
|
|
|
|
|
character: whitespace_characters.newline,
|
|
|
|
|
|
|
|
padding: None,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|