mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 18:36:18 +04:00
Compare commits
9 Commits
f2950e4128
...
1105495466
Author | SHA1 | Date | |
---|---|---|---|
|
1105495466 | ||
|
a37f70a488 | ||
|
ab57115567 | ||
|
970122f6d2 | ||
|
a86a7d6920 | ||
|
ba893751c2 | ||
|
de13260bb6 | ||
|
99aa751c75 | ||
|
60c06076b2 |
@ -8,13 +8,15 @@
|
|||||||
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};
|
||||||
use helix_stdx::rope::RopeSliceExt;
|
use helix_stdx::rope::RopeSliceExt;
|
||||||
use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue};
|
use helix_view::editor::WhitespaceFeature;
|
||||||
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, Theme};
|
use helix_view::{Document, Theme};
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::buffer::Buffer as Surface;
|
||||||
|
|
||||||
|
use super::trailing_whitespace::{TrailingWhitespaceTracker, WhitespaceKind};
|
||||||
|
|
||||||
use crate::ui::text_decorations::DecorationManager;
|
use crate::ui::text_decorations::DecorationManager;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
@ -254,6 +256,7 @@ pub struct TextRenderer<'a> {
|
|||||||
surface: &'a mut Surface,
|
surface: &'a mut Surface,
|
||||||
pub text_style: Style,
|
pub text_style: Style,
|
||||||
pub whitespace_style: Style,
|
pub whitespace_style: Style,
|
||||||
|
pub trailing_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 newline: String,
|
||||||
@ -267,6 +270,7 @@ pub struct TextRenderer<'a> {
|
|||||||
pub draw_indent_guides: bool,
|
pub draw_indent_guides: bool,
|
||||||
pub viewport: Rect,
|
pub viewport: Rect,
|
||||||
pub offset: Position,
|
pub offset: Position,
|
||||||
|
pub trailing_whitespace_tracker: TrailingWhitespaceTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GraphemeStyle {
|
pub struct GraphemeStyle {
|
||||||
@ -283,56 +287,27 @@ pub fn new(
|
|||||||
viewport: Rect,
|
viewport: Rect,
|
||||||
) -> TextRenderer<'a> {
|
) -> TextRenderer<'a> {
|
||||||
let editor_config = doc.config.load();
|
let editor_config = doc.config.load();
|
||||||
let WhitespaceConfig {
|
|
||||||
render: ws_render,
|
|
||||||
characters: ws_chars,
|
|
||||||
} = &editor_config.whitespace;
|
|
||||||
|
|
||||||
let tab_width = doc.tab_width();
|
let tab_width = doc.tab_width();
|
||||||
let tab = if ws_render.tab() == WhitespaceRenderValue::All {
|
|
||||||
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;
|
||||||
|
|
||||||
|
let ws = &editor_config.whitespace;
|
||||||
|
let regular_ws = WhitespaceFeature::Regular.palette(ws, tab_width);
|
||||||
|
let trailing_ws = WhitespaceFeature::Trailing.palette(ws, tab_width);
|
||||||
|
let trailing_whitespace_tracker = TrailingWhitespaceTracker::new(ws.render, trailing_ws);
|
||||||
|
|
||||||
TextRenderer {
|
TextRenderer {
|
||||||
surface,
|
surface,
|
||||||
indent_guide_char: editor_config.indent_guides.character.into(),
|
indent_guide_char: editor_config.indent_guides.character.into(),
|
||||||
newline,
|
newline: regular_ws.newline,
|
||||||
nbsp,
|
nbsp: regular_ws.nbsp,
|
||||||
nnbsp,
|
nnbsp: regular_ws.nnbsp,
|
||||||
space,
|
space: regular_ws.space,
|
||||||
tab,
|
tab: regular_ws.tab,
|
||||||
virtual_tab,
|
virtual_tab: regular_ws.virtual_tab,
|
||||||
whitespace_style: theme.get("ui.virtual.whitespace"),
|
whitespace_style: theme.get("ui.virtual.whitespace"),
|
||||||
|
trailing_whitespace_style: theme.get("ui.virtual.trailing_whitespace"),
|
||||||
indent_width,
|
indent_width,
|
||||||
starting_indent: offset.col / indent_width as usize
|
starting_indent: offset.col / indent_width as usize
|
||||||
+ (offset.col % indent_width as usize != 0) as usize
|
+ (offset.col % indent_width as usize != 0) as usize
|
||||||
@ -346,6 +321,7 @@ pub fn new(
|
|||||||
draw_indent_guides: editor_config.indent_guides.render,
|
draw_indent_guides: editor_config.indent_guides.render,
|
||||||
viewport,
|
viewport,
|
||||||
offset,
|
offset,
|
||||||
|
trailing_whitespace_tracker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Draws a single `grapheme` at the current render position with a specified `style`.
|
/// Draws a single `grapheme` at the current render position with a specified `style`.
|
||||||
@ -420,28 +396,61 @@ pub fn draw_grapheme(
|
|||||||
} else {
|
} else {
|
||||||
&self.tab
|
&self.tab
|
||||||
};
|
};
|
||||||
|
let mut whitespace_kind = WhitespaceKind::None;
|
||||||
let grapheme = match grapheme {
|
let grapheme = match grapheme {
|
||||||
Grapheme::Tab { width } => {
|
Grapheme::Tab { width } => {
|
||||||
|
whitespace_kind = WhitespaceKind::Tab;
|
||||||
let grapheme_tab_width = char_to_byte_idx(tab, width);
|
let grapheme_tab_width = char_to_byte_idx(tab, width);
|
||||||
&tab[..grapheme_tab_width]
|
&tab[..grapheme_tab_width]
|
||||||
}
|
}
|
||||||
// TODO special rendering for other whitespaces?
|
// TODO special rendering for other whitespaces?
|
||||||
Grapheme::Other { ref g } if g == " " => space,
|
Grapheme::Other { ref g } if g == " " => {
|
||||||
Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp,
|
whitespace_kind = WhitespaceKind::Space;
|
||||||
Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp,
|
space
|
||||||
|
}
|
||||||
|
Grapheme::Other { ref g } if g == "\u{00A0}" => {
|
||||||
|
whitespace_kind = WhitespaceKind::NonBreakingSpace;
|
||||||
|
nbsp
|
||||||
|
}
|
||||||
|
Grapheme::Other { ref g } if g == "\u{202F}" => {
|
||||||
|
whitespace_kind = WhitespaceKind::NarrowNonBreakingSpace;
|
||||||
|
nnbsp
|
||||||
|
}
|
||||||
Grapheme::Other { ref g } => g,
|
Grapheme::Other { ref g } => g,
|
||||||
Grapheme::Newline => &self.newline,
|
Grapheme::Newline => {
|
||||||
|
whitespace_kind = WhitespaceKind::Newline;
|
||||||
|
&self.newline
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let viewport_right_edge = self.viewport.width as usize + self.offset.col - 1;
|
||||||
let in_bounds = self.column_in_bounds(position.col, width);
|
let in_bounds = self.column_in_bounds(position.col, width);
|
||||||
|
|
||||||
if in_bounds {
|
if in_bounds {
|
||||||
|
let in_bounds_col = position.col - self.offset.col;
|
||||||
self.surface.set_string(
|
self.surface.set_string(
|
||||||
self.viewport.x + (position.col - self.offset.col) as u16,
|
self.viewport.x + in_bounds_col as u16,
|
||||||
self.viewport.y + position.row as u16,
|
self.viewport.y + position.row as u16,
|
||||||
grapheme,
|
grapheme,
|
||||||
style,
|
style,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self
|
||||||
|
.trailing_whitespace_tracker
|
||||||
|
.track(in_bounds_col, whitespace_kind)
|
||||||
|
|| position.col == viewport_right_edge
|
||||||
|
{
|
||||||
|
self.trailing_whitespace_tracker.render(
|
||||||
|
&mut |trailing_whitespace: &str, from: usize| {
|
||||||
|
self.surface.set_string(
|
||||||
|
self.viewport.x + from as u16,
|
||||||
|
self.viewport.y + position.row as u16,
|
||||||
|
trailing_whitespace,
|
||||||
|
style.patch(self.trailing_whitespace_style),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if cut_off_start != 0 && cut_off_start < width {
|
} else if cut_off_start != 0 && cut_off_start < width {
|
||||||
// partially on screen
|
// partially on screen
|
||||||
let rect = Rect::new(
|
let rect = Rect::new(
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
mod statusline;
|
mod statusline;
|
||||||
mod text;
|
mod text;
|
||||||
mod text_decorations;
|
mod text_decorations;
|
||||||
|
mod trailing_whitespace;
|
||||||
|
|
||||||
use crate::compositor::Compositor;
|
use crate::compositor::Compositor;
|
||||||
use crate::filter_picker_entry;
|
use crate::filter_picker_entry;
|
||||||
|
173
helix-term/src/ui/trailing_whitespace.rs
Normal file
173
helix-term/src/ui/trailing_whitespace.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
use helix_core::str_utils::char_to_byte_idx;
|
||||||
|
use helix_view::editor::{WhitespacePalette, WhitespaceRender, WhitespaceRenderValue};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum WhitespaceKind {
|
||||||
|
None,
|
||||||
|
Space,
|
||||||
|
NonBreakingSpace,
|
||||||
|
NarrowNonBreakingSpace,
|
||||||
|
Tab,
|
||||||
|
Newline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhitespaceKind {
|
||||||
|
pub fn to_str(self, palette: &WhitespacePalette) -> &str {
|
||||||
|
match self {
|
||||||
|
WhitespaceKind::Space => &palette.space,
|
||||||
|
WhitespaceKind::NonBreakingSpace => &palette.nbsp,
|
||||||
|
WhitespaceKind::NarrowNonBreakingSpace => &palette.nnbsp,
|
||||||
|
WhitespaceKind::Tab => {
|
||||||
|
let grapheme_tab_width = char_to_byte_idx(&palette.tab, palette.tab.len());
|
||||||
|
&palette.tab[..grapheme_tab_width]
|
||||||
|
}
|
||||||
|
WhitespaceKind::Newline | WhitespaceKind::None => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrailingWhitespaceTracker {
|
||||||
|
enabled: bool,
|
||||||
|
palette: WhitespacePalette,
|
||||||
|
tracking_from: usize,
|
||||||
|
tracking_content: Vec<(WhitespaceKind, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrailingWhitespaceTracker {
|
||||||
|
pub fn new(render: WhitespaceRender, palette: WhitespacePalette) -> Self {
|
||||||
|
Self {
|
||||||
|
palette,
|
||||||
|
enabled: render.any(WhitespaceRenderValue::Trailing),
|
||||||
|
tracking_from: 0,
|
||||||
|
tracking_content: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracks the whitespace and returns wether [`render`] should be called right after
|
||||||
|
// to display the trailing whitespace.
|
||||||
|
pub fn track(&mut self, from: usize, kind: WhitespaceKind) -> bool {
|
||||||
|
if !self.enabled || kind == WhitespaceKind::None {
|
||||||
|
self.tracking_content.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if kind == WhitespaceKind::Newline {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if self.tracking_content.is_empty() {
|
||||||
|
self.tracking_from = from;
|
||||||
|
}
|
||||||
|
self.compress(kind);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self, callback: &mut impl FnMut(&str, usize)) {
|
||||||
|
if self.tracking_content.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut offset = self.tracking_from;
|
||||||
|
self.tracking_content.iter().for_each(|(kind, n)| {
|
||||||
|
let ws = kind.to_str(&self.palette).repeat(*n);
|
||||||
|
callback(&ws, offset);
|
||||||
|
offset += n;
|
||||||
|
});
|
||||||
|
self.tracking_content.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compress(&mut self, kind: WhitespaceKind) {
|
||||||
|
if let Some((last_kind, n)) = self.tracking_content.last_mut() {
|
||||||
|
if *last_kind == kind {
|
||||||
|
*n += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tracking_content.push((kind, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use helix_view::editor::WhitespaceRender;
|
||||||
|
|
||||||
|
fn palette() -> WhitespacePalette {
|
||||||
|
WhitespacePalette {
|
||||||
|
space: "S".into(),
|
||||||
|
nbsp: "N".into(),
|
||||||
|
nnbsp: "M".into(),
|
||||||
|
tab: "<TAB>".into(),
|
||||||
|
virtual_tab: "V".into(),
|
||||||
|
newline: "L".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capture(sut: &mut TrailingWhitespaceTracker) -> (String, usize, usize) {
|
||||||
|
let mut captured_content = String::new();
|
||||||
|
let mut from: usize = 0;
|
||||||
|
let mut to: usize = 0;
|
||||||
|
|
||||||
|
sut.render(&mut |content: &str, pos: usize| {
|
||||||
|
captured_content.push_str(content);
|
||||||
|
if from == 0 {
|
||||||
|
from = pos;
|
||||||
|
}
|
||||||
|
to = pos;
|
||||||
|
});
|
||||||
|
|
||||||
|
(captured_content, from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trailing_whitespace_tracker_correctly_tracks_sequences() {
|
||||||
|
let ws_render = WhitespaceRender::Basic(WhitespaceRenderValue::Trailing);
|
||||||
|
|
||||||
|
let mut sut = TrailingWhitespaceTracker::new(ws_render, palette());
|
||||||
|
|
||||||
|
sut.track(5, WhitespaceKind::Space);
|
||||||
|
sut.track(6, WhitespaceKind::NonBreakingSpace);
|
||||||
|
sut.track(7, WhitespaceKind::NarrowNonBreakingSpace);
|
||||||
|
sut.track(8, WhitespaceKind::Tab);
|
||||||
|
|
||||||
|
let (content, from, to) = capture(&mut sut);
|
||||||
|
|
||||||
|
assert_eq!(5, from);
|
||||||
|
assert_eq!(8, to);
|
||||||
|
assert_eq!("SNM<TAB>", content);
|
||||||
|
|
||||||
|
// Now we break the sequence
|
||||||
|
sut.track(6, WhitespaceKind::None);
|
||||||
|
|
||||||
|
let (content, from, to) = capture(&mut sut);
|
||||||
|
assert_eq!(0, from);
|
||||||
|
assert_eq!(0, to);
|
||||||
|
assert_eq!("", content);
|
||||||
|
|
||||||
|
sut.track(10, WhitespaceKind::Tab);
|
||||||
|
sut.track(11, WhitespaceKind::NonBreakingSpace);
|
||||||
|
sut.track(12, WhitespaceKind::NarrowNonBreakingSpace);
|
||||||
|
sut.track(13, WhitespaceKind::Space);
|
||||||
|
|
||||||
|
let (content, from, to) = capture(&mut sut);
|
||||||
|
assert_eq!(10, from);
|
||||||
|
assert_eq!(13, to);
|
||||||
|
assert_eq!("<TAB>NMS", content);
|
||||||
|
|
||||||
|
// Verify compression works
|
||||||
|
sut.track(20, WhitespaceKind::Space);
|
||||||
|
sut.track(21, WhitespaceKind::Space);
|
||||||
|
sut.track(22, WhitespaceKind::NonBreakingSpace);
|
||||||
|
sut.track(23, WhitespaceKind::NonBreakingSpace);
|
||||||
|
sut.track(24, WhitespaceKind::NarrowNonBreakingSpace);
|
||||||
|
sut.track(25, WhitespaceKind::NarrowNonBreakingSpace);
|
||||||
|
sut.track(26, WhitespaceKind::Tab);
|
||||||
|
sut.track(27, WhitespaceKind::Tab);
|
||||||
|
sut.track(28, WhitespaceKind::Tab);
|
||||||
|
|
||||||
|
let (content, from, to) = capture(&mut sut);
|
||||||
|
assert_eq!(20, from);
|
||||||
|
assert_eq!(26, to); // Compression means last tracked token is on 26 instead of 28
|
||||||
|
assert_eq!("SSNNMM<TAB><TAB><TAB>", content);
|
||||||
|
}
|
||||||
|
}
|
@ -731,13 +731,88 @@ pub enum WhitespaceRender {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WhitespaceRender {
|
||||||
|
pub fn any(&self, value: WhitespaceRenderValue) -> bool {
|
||||||
|
self.space() == value
|
||||||
|
|| self.nbsp() == value
|
||||||
|
|| self.nnbsp() == value
|
||||||
|
|| self.tab() == value
|
||||||
|
|| self.newline() == value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WhitespaceFeature {
|
||||||
|
Regular,
|
||||||
|
Trailing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhitespaceFeature {
|
||||||
|
pub fn is_enabled(&self, render: WhitespaceRenderValue) -> bool {
|
||||||
|
match self {
|
||||||
|
WhitespaceFeature::Regular => matches!(render, WhitespaceRenderValue::All),
|
||||||
|
WhitespaceFeature::Trailing => matches!(
|
||||||
|
render,
|
||||||
|
WhitespaceRenderValue::All | WhitespaceRenderValue::Trailing
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn palette(self, cfg: &WhitespaceConfig, tab_width: usize) -> WhitespacePalette {
|
||||||
|
WhitespacePalette::from(self, cfg, tab_width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WhitespacePalette {
|
||||||
|
pub space: String,
|
||||||
|
pub nbsp: String,
|
||||||
|
pub nnbsp: String,
|
||||||
|
pub tab: String,
|
||||||
|
pub virtual_tab: String,
|
||||||
|
pub newline: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhitespacePalette {
|
||||||
|
fn from(feature: WhitespaceFeature, cfg: &WhitespaceConfig, tab_width: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
space: if feature.is_enabled(cfg.render.space()) {
|
||||||
|
cfg.characters.space.to_string()
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
},
|
||||||
|
nbsp: if feature.is_enabled(cfg.render.nbsp()) {
|
||||||
|
cfg.characters.nbsp.to_string()
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
},
|
||||||
|
nnbsp: if feature.is_enabled(cfg.render.nnbsp()) {
|
||||||
|
cfg.characters.nnbsp.to_string()
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
},
|
||||||
|
tab: if feature.is_enabled(cfg.render.tab()) {
|
||||||
|
cfg.characters.generate_tab(tab_width)
|
||||||
|
} else {
|
||||||
|
" ".repeat(tab_width)
|
||||||
|
},
|
||||||
|
newline: if feature.is_enabled(cfg.render.newline()) {
|
||||||
|
cfg.characters.newline.to_string()
|
||||||
|
} else {
|
||||||
|
" ".to_string()
|
||||||
|
},
|
||||||
|
virtual_tab: " ".repeat(tab_width),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum WhitespaceRenderValue {
|
pub enum WhitespaceRenderValue {
|
||||||
None,
|
None,
|
||||||
|
All,
|
||||||
|
Trailing,
|
||||||
// TODO
|
// TODO
|
||||||
// Selection,
|
// Selection,
|
||||||
All,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WhitespaceRender {
|
impl WhitespaceRender {
|
||||||
@ -862,6 +937,14 @@ fn default() -> Self {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WhitespaceCharacters {
|
||||||
|
pub fn generate_tab(&self, width: usize) -> String {
|
||||||
|
std::iter::once(self.tab)
|
||||||
|
.chain(std::iter::repeat(self.tabpad).take(width - 1))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "kebab-case")]
|
#[serde(default, rename_all = "kebab-case")]
|
||||||
pub struct IndentGuidesConfig {
|
pub struct IndentGuidesConfig {
|
||||||
@ -2225,3 +2308,95 @@ pub fn reset(&self) {
|
|||||||
self.0.set(None)
|
self.0.set(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_render_any() {
|
||||||
|
let sut = WhitespaceRender::Basic(WhitespaceRenderValue::Trailing);
|
||||||
|
assert!(!sut.any(WhitespaceRenderValue::None));
|
||||||
|
assert!(!sut.any(WhitespaceRenderValue::All));
|
||||||
|
assert!(sut.any(WhitespaceRenderValue::Trailing));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_feature_is_enabled_regular() {
|
||||||
|
let sut = WhitespaceFeature::Regular;
|
||||||
|
|
||||||
|
assert!(!sut.is_enabled(WhitespaceRenderValue::None));
|
||||||
|
assert!(!sut.is_enabled(WhitespaceRenderValue::Trailing));
|
||||||
|
assert!(sut.is_enabled(WhitespaceRenderValue::All));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_feature_is_enabled_trailing() {
|
||||||
|
let sut = WhitespaceFeature::Trailing;
|
||||||
|
|
||||||
|
assert!(!sut.is_enabled(WhitespaceRenderValue::None));
|
||||||
|
assert!(sut.is_enabled(WhitespaceRenderValue::Trailing));
|
||||||
|
assert!(sut.is_enabled(WhitespaceRenderValue::All));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_palette_regular_all() {
|
||||||
|
let cfg = WhitespaceConfig {
|
||||||
|
render: WhitespaceRender::Basic(WhitespaceRenderValue::All),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sut = WhitespacePalette::from(WhitespaceFeature::Regular, &cfg, 2);
|
||||||
|
|
||||||
|
assert_eq!("·", sut.space);
|
||||||
|
assert_eq!("⍽", sut.nbsp);
|
||||||
|
assert_eq!("␣", sut.nnbsp);
|
||||||
|
assert_eq!("→ ", sut.tab);
|
||||||
|
assert_eq!(" ", sut.virtual_tab);
|
||||||
|
assert_eq!("⏎", sut.newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_palette_regular_trailing() {
|
||||||
|
let cfg = WhitespaceConfig {
|
||||||
|
render: WhitespaceRender::Basic(WhitespaceRenderValue::Trailing),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sut = WhitespacePalette::from(WhitespaceFeature::Regular, &cfg, 2);
|
||||||
|
|
||||||
|
assert_eq!(" ", sut.space);
|
||||||
|
assert_eq!(" ", sut.nbsp);
|
||||||
|
assert_eq!(" ", sut.nnbsp);
|
||||||
|
assert_eq!(" ", sut.tab);
|
||||||
|
assert_eq!(" ", sut.virtual_tab);
|
||||||
|
assert_eq!(" ", sut.newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_palette_trailing_all() {
|
||||||
|
let cfg = WhitespaceConfig {
|
||||||
|
render: WhitespaceRender::Basic(WhitespaceRenderValue::All),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sut = WhitespacePalette::from(WhitespaceFeature::Trailing, &cfg, 2);
|
||||||
|
|
||||||
|
assert_eq!("·", sut.space);
|
||||||
|
assert_eq!("⍽", sut.nbsp);
|
||||||
|
assert_eq!("␣", sut.nnbsp);
|
||||||
|
assert_eq!("→ ", sut.tab);
|
||||||
|
assert_eq!(" ", sut.virtual_tab);
|
||||||
|
assert_eq!("⏎", sut.newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_whitespace_characters_render_tab() {
|
||||||
|
let sut = WhitespaceCharacters::default();
|
||||||
|
|
||||||
|
assert_eq!("→", sut.generate_tab(1));
|
||||||
|
assert_eq!("→ ", sut.generate_tab(2));
|
||||||
|
assert_eq!("→ ", sut.generate_tab(3));
|
||||||
|
assert_eq!("→ ", sut.generate_tab(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user