|
|
|
@ -124,7 +124,15 @@ impl EditorView {
|
|
|
|
|
Box::new(highlights)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Self::render_text_highlights(doc, view.offset, inner, surface, theme, highlights);
|
|
|
|
|
Self::render_text_highlights(
|
|
|
|
|
doc,
|
|
|
|
|
view.offset,
|
|
|
|
|
inner,
|
|
|
|
|
surface,
|
|
|
|
|
theme,
|
|
|
|
|
highlights,
|
|
|
|
|
&editor.config().whitespace,
|
|
|
|
|
);
|
|
|
|
|
Self::render_gutter(editor, doc, view, view.area, surface, theme, is_focused);
|
|
|
|
|
Self::render_rulers(editor, doc, view, inner, surface, theme);
|
|
|
|
|
|
|
|
|
@ -344,7 +352,10 @@ impl EditorView {
|
|
|
|
|
surface: &mut Surface,
|
|
|
|
|
theme: &Theme,
|
|
|
|
|
highlights: H,
|
|
|
|
|
whitespace: &helix_view::editor::WhitespaceConfig,
|
|
|
|
|
) {
|
|
|
|
|
use helix_view::editor::WhitespaceRenderValue;
|
|
|
|
|
|
|
|
|
|
// It's slightly more efficient to produce a full RopeSlice from the Rope, then slice that a bunch
|
|
|
|
|
// of times than it is to always call Rope::slice/get_slice (it will internally always hit RSEnum::Light).
|
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
@ -353,9 +364,20 @@ impl EditorView {
|
|
|
|
|
let mut visual_x = 0u16;
|
|
|
|
|
let mut line = 0u16;
|
|
|
|
|
let tab_width = doc.tab_width();
|
|
|
|
|
let tab = " ".repeat(tab_width);
|
|
|
|
|
let tab = if whitespace.render.tab() == WhitespaceRenderValue::All {
|
|
|
|
|
(1..tab_width).fold(whitespace.characters.tab.to_string(), |s, _| s + " ")
|
|
|
|
|
} else {
|
|
|
|
|
" ".repeat(tab_width)
|
|
|
|
|
};
|
|
|
|
|
let space = whitespace.characters.space.to_string();
|
|
|
|
|
let newline = if whitespace.render.newline() == WhitespaceRenderValue::All {
|
|
|
|
|
whitespace.characters.newline.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
" ".to_string()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let text_style = theme.get("ui.text");
|
|
|
|
|
let whitespace_style = theme.get("ui.virtual.whitespace");
|
|
|
|
|
|
|
|
|
|
'outer: for event in highlights {
|
|
|
|
|
match event {
|
|
|
|
@ -374,6 +396,14 @@ impl EditorView {
|
|
|
|
|
.iter()
|
|
|
|
|
.fold(text_style, |acc, span| acc.patch(theme.highlight(span.0)));
|
|
|
|
|
|
|
|
|
|
let space = if whitespace.render.space() == WhitespaceRenderValue::All
|
|
|
|
|
&& text.len_chars() < end
|
|
|
|
|
{
|
|
|
|
|
&space
|
|
|
|
|
} else {
|
|
|
|
|
" "
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
|
|
|
|
|
|
|
|
|
|
for grapheme in RopeGraphemes::new(text) {
|
|
|
|
@ -386,8 +416,8 @@ impl EditorView {
|
|
|
|
|
surface.set_string(
|
|
|
|
|
viewport.x + visual_x - offset.col as u16,
|
|
|
|
|
viewport.y + line,
|
|
|
|
|
" ",
|
|
|
|
|
style,
|
|
|
|
|
&newline,
|
|
|
|
|
style.patch(whitespace_style),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -400,12 +430,21 @@ impl EditorView {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let grapheme = Cow::from(grapheme);
|
|
|
|
|
let is_whitespace;
|
|
|
|
|
|
|
|
|
|
let (grapheme, width) = if grapheme == "\t" {
|
|
|
|
|
is_whitespace = true;
|
|
|
|
|
// make sure we display tab as appropriate amount of spaces
|
|
|
|
|
let visual_tab_width = tab_width - (visual_x as usize % tab_width);
|
|
|
|
|
(&tab[..visual_tab_width], visual_tab_width)
|
|
|
|
|
let grapheme_tab_width =
|
|
|
|
|
ropey::str_utils::char_to_byte_idx(&tab, visual_tab_width);
|
|
|
|
|
|
|
|
|
|
(&tab[..grapheme_tab_width], visual_tab_width)
|
|
|
|
|
} else if grapheme == " " {
|
|
|
|
|
is_whitespace = true;
|
|
|
|
|
(space, 1)
|
|
|
|
|
} else {
|
|
|
|
|
is_whitespace = false;
|
|
|
|
|
// Cow will prevent allocations if span contained in a single slice
|
|
|
|
|
// which should really be the majority case
|
|
|
|
|
let width = grapheme_width(&grapheme);
|
|
|
|
@ -418,7 +457,11 @@ impl EditorView {
|
|
|
|
|
viewport.x + visual_x - offset.col as u16,
|
|
|
|
|
viewport.y + line,
|
|
|
|
|
grapheme,
|
|
|
|
|
style,
|
|
|
|
|
if is_whitespace {
|
|
|
|
|
style.patch(whitespace_style)
|
|
|
|
|
} else {
|
|
|
|
|
style
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|