feat: render selected whitespace only

pull/11516/head
sunshine 3 months ago
parent b90ec5c779
commit 580d9d28f7

@ -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,
},
}
}
}

@ -201,10 +201,16 @@ impl EditorView {
inline_diagnostic_config, inline_diagnostic_config,
config.end_of_line_diagnostics, config.end_of_line_diagnostics,
)); ));
let selection = if is_focused {
Some(doc.selection(view.id))
} else {
None
};
render_document( render_document(
surface, surface,
inner, inner,
doc, doc,
selection,
view_offset, view_offset,
&text_annotations, &text_annotations,
syntax_highlights, syntax_highlights,

@ -935,6 +935,7 @@ impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
surface, surface,
inner, inner,
doc, doc,
None,
offset, offset,
// TODO: compute text annotations asynchronously here (like inlay hints) // TODO: compute text annotations asynchronously here (like inlay hints)
&TextAnnotations::default(), &TextAnnotations::default(),

@ -732,8 +732,7 @@ pub enum WhitespaceRender {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum WhitespaceRenderValue { pub enum WhitespaceRenderValue {
None, None,
// TODO Selection,
// Selection,
All, All,
} }

Loading…
Cancel
Save