From 74edf72cde7f1ff8be12c3449bb415a9e326137c Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Thu, 1 Jan 2015 17:35:34 -0800 Subject: [PATCH] First attempt at support for variable-width graphemes. Doesn't completely work, but it's on the right track. --- src/buffer/line.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++- src/term_ui.rs | 22 ++++++++--- 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/buffer/line.rs b/src/buffer/line.rs index 55f0466..13fe5ff 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -4,6 +4,30 @@ use std::mem; use std::str::Graphemes; use string_utils::{grapheme_count, grapheme_pos_to_byte_pos, is_line_ending}; +const TAB_WIDTH: uint = 4; + + +/// Returns the visual width of a grapheme given a starting +/// position on a line. +fn grapheme_vis_width_at_vis_pos(g: &str, pos: uint) -> uint { + match g { + "\t" => { + let ending_pos = ((pos / TAB_WIDTH) + 1) * TAB_WIDTH; + return ending_pos - pos; + }, + + _ => { + if is_line_ending(g) { + return 1; + } + else { + return g.width(true); + } + } + } +} + + /// A single line of text pub struct Line { @@ -208,6 +232,19 @@ impl Line { } + /// Returns the visual cell width of the line + pub fn vis_width(&self) -> uint { + let mut width = 0; + + for g in self.as_str().graphemes(true) { + let w = grapheme_vis_width_at_vis_pos(g, width); + width += w; + } + + return width; + } + + /// Returns an immutable string slice into the text block's memory pub fn as_str<'a>(&'a self) -> &'a str { unsafe { @@ -357,6 +394,15 @@ impl Line { return iter; } + + + /// Returns an iterator over the graphemes of the line + pub fn grapheme_vis_iter<'a>(&'a self) -> LineGraphemeVisIter<'a> { + LineGraphemeVisIter { + graphemes: self.grapheme_iter(), + vis_pos: 0, + } + } } @@ -456,7 +502,9 @@ pub struct LineGraphemeIter<'a> { impl<'a> LineGraphemeIter<'a> { pub fn skip_graphemes(&mut self, n: uint) { for _ in range(0, n) { - self.next(); + if let None = self.next() { + break; + } } } } @@ -488,6 +536,54 @@ impl<'a> Iterator<&'a str> for LineGraphemeIter<'a> { + +/// An iterator over the graphemes of a Line. This iterator yields not just +/// the grapheme, but also it's beginning visual position in the line and its +/// visual width. +pub struct LineGraphemeVisIter<'a> { + graphemes: LineGraphemeIter<'a>, + vis_pos: uint, +} + +impl<'a> LineGraphemeVisIter<'a> { + pub fn skip_graphemes(&mut self, n: uint) { + for _ in range(0, n) { + if let None = self.next() { + break; + } + } + } + + pub fn skip_vis_positions(&mut self, n: uint) { + let mut i = 0; + while i < n { + if let Some((_, _, width)) = self.next() { + i += width; + } + else { + break; + } + } + } +} + +impl<'a> Iterator<(&'a str, uint, uint)> for LineGraphemeVisIter<'a> { + fn next(&mut self) -> Option<(&'a str, uint, uint)> { + if let Some(g) = self.graphemes.next() { + let pos = self.vis_pos; + let width = grapheme_vis_width_at_vis_pos(g, self.vis_pos); + self.vis_pos += width; + return Some((g, pos, width)); + } + else { + return None; + } + } +} + + + + //========================================================================= // Line tests //========================================================================= diff --git a/src/term_ui.rs b/src/term_ui.rs index 3a87e79..af8f4c1 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -158,7 +158,7 @@ impl TermUI { let mut col_num = editor.view_pos.1; let mut print_line_num = c1.0; - let mut print_col_num = c1.1; + let mut print_col_num; let max_print_line = c2.0 - c1.0; let max_print_col = c2.1 - c1.1; @@ -169,15 +169,26 @@ impl TermUI { loop { if let Some(line) = line_iter.next() { - let mut g_iter = line.grapheme_iter(); - g_iter.skip_graphemes(editor.view_pos.1); + let mut g_iter = line.grapheme_vis_iter(); + g_iter.skip_vis_positions(editor.view_pos.1); - for g in g_iter { + for (g, pos, width) in g_iter { + print_col_num = pos - editor.view_pos.1; + if is_line_ending(g) { if (line_num, col_num) == cursor_pos { self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); } } + else if g == "\t" { + for i in range(print_col_num, print_col_num + width) { + self.rb.print(i, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, " "); + } + + if (line_num, col_num) == cursor_pos { + self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + } + } else { if (line_num, col_num) == cursor_pos { self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g); @@ -188,7 +199,7 @@ impl TermUI { } col_num += 1; - print_col_num += 1; + print_col_num += width; if print_col_num > max_print_col { break; @@ -205,7 +216,6 @@ impl TermUI { line_num += 1; print_line_num += 1; col_num = editor.view_pos.1; - print_col_num = c1.1; if print_line_num > max_print_line { break;