From e3e2c866b9dc2fe1659b4e5d8ef6215f626814ca Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 28 Dec 2014 13:44:38 -0800 Subject: [PATCH] We now store character widths in TextBlocks. This is the first step towards supporting proportional fonts and tabs characters. --- src/buffer/text_block.rs | 97 +++++++++++++++++++++++++++++++++++++--- todo.md | 4 +- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/buffer/text_block.rs b/src/buffer/text_block.rs index 899f91f..2f86efa 100644 --- a/src/buffer/text_block.rs +++ b/src/buffer/text_block.rs @@ -2,31 +2,49 @@ use std::mem; use std::fmt; -use string_utils::char_pos_to_byte_pos; +use string_utils::{char_pos_to_byte_pos, char_count}; /// A block of text, contiguous in memory pub struct TextBlock { + // The actual text data, in utf8 pub data: Vec, + + // The visual width of each printable character. + // Characters with variable width (e.g. tab characters) + // have width None. + pub widths: Vec>, } impl TextBlock { /// Create a new empty text block. pub fn new() -> TextBlock { TextBlock { - data: Vec::::new() + data: Vec::::new(), + widths: Vec::>::new(), } } /// Create a new text block with the contents of 'text'. pub fn new_from_str(text: &str) -> TextBlock { let mut tb = TextBlock { - data: Vec::::with_capacity(text.len()) + data: Vec::::with_capacity(text.len()), + widths: Vec::>::with_capacity(text.len()), }; for b in text.bytes() { tb.data.push(b); } + // TODO: handle fonts + for c in text.chars() { + if c == '\t' { + tb.widths.push(None); + } + else { + tb.widths.push(Some(1)); + } + } + return tb; } @@ -35,8 +53,35 @@ impl TextBlock { self.data.len() } + /// Returns the total width of text block sans-variable-width characters + pub fn total_non_variable_width(&self) -> uint { + let mut width: uint = 0; + + for w in self.widths.iter() { + if let &Some(ww) = w { + width += ww as uint; + } + } + + return width; + } + + /// Returns the number of variable-width chars in the text block + pub fn variable_width_chars(&self) -> uint { + let mut count: uint = 0; + + for w in self.widths.iter() { + if let &None = w { + count += 1; + } + } + + return count; + } + /// Insert 'text' at char position 'pos'. pub fn insert_text(&mut self, text: &str, pos: uint) { + //====== TEXT DATA ====== // Find insertion position in bytes let byte_pos = char_pos_to_byte_pos(self.as_str(), pos); @@ -59,6 +104,33 @@ impl TextBlock { self.data[i] = b; i += 1 } + + //====== WIDTHS ====== + // Grow widths size + let cc = char_count(text); + self.widths.grow(cc, None); + + // Move old widths forward + from = self.widths.len() - cc; + to = self.widths.len(); + while from > pos { + from -= 1; + to -= 1; + + self.widths[to] = self.widths[from]; + } + + // Copy new widths in + i = pos; + for c in text.chars() { + if c == '\t' { + self.widths[i] = None; + } + else { + self.widths[i] = Some(1); + } + i += 1 + } } /// Remove the text between char positions 'pos_a' and 'pos_b'. @@ -68,6 +140,7 @@ impl TextBlock { panic!("TextBlock::remove_text(): pos_a must be less than or equal to pos_b."); } + //====== TEXT DATA ====== // Find removal positions in bytes let byte_pos_a = char_pos_to_byte_pos(self.as_str(), pos_a); let byte_pos_b = char_pos_to_byte_pos(self.as_str(), pos_b); @@ -83,8 +156,22 @@ impl TextBlock { } // Remove data from the end - let final_size = self.data.len() + byte_pos_a - byte_pos_b; - self.data.truncate(final_size); + let final_data_size = self.data.len() + byte_pos_a - byte_pos_b; + self.data.truncate(final_data_size); + + //====== WIDTHS ====== + from = pos_b; + to = pos_a; + while from < self.widths.len() { + self.widths[to] = self.widths[from]; + + from += 1; + to += 1; + } + + // Remove data from end + let final_widths_size = self.widths.len() + pos_a - pos_b; + self.data.truncate(final_widths_size); } /// Returns an immutable string slice into the text block's memory diff --git a/todo.md b/todo.md index 1089d84..2e4c43a 100644 --- a/todo.md +++ b/todo.md @@ -1,4 +1,6 @@ -- Proper handling of tab characters +- Proper handling of non-uniform-width characters. Specifically, this needs + to address tabs. But it should be done to handle the general case anyway, + since that's unlikely to be more complex and will future-proof things. - Line number display - Editor info display (filename, current line/column, indentation style, etc.) - File opening by entering path