Non-uniform-width characters are now properly handled (modulo bugs).

This commit is contained in:
Nathan Vegdahl 2015-01-02 15:05:21 -08:00
parent 74edf72cde
commit fc78fbeb3e
5 changed files with 243 additions and 67 deletions

View File

@ -245,6 +245,56 @@ impl Line {
} }
/// Translates a grapheme index into a visual horizontal position
pub fn grapheme_index_to_closest_vis_pos(&self, index: uint) -> uint {
let mut pos = 0;
let mut iter = self.as_str().graphemes(true);
for _ in range(0, index) {
if let Some(g) = iter.next() {
let w = grapheme_vis_width_at_vis_pos(g, pos);
pos += w;
}
else {
panic!("Line::grapheme_index_to_vis_pos(): index past end of line.");
}
}
return pos;
}
/// Translates a visual horizontal position to the closest grapheme index
pub fn vis_pos_to_closest_grapheme_index(&self, vis_pos: uint) -> uint {
let mut pos = 0;
let mut i = 0;
let mut iter = self.as_str().graphemes(true);
while pos < vis_pos {
if let Some(g) = iter.next() {
let w = grapheme_vis_width_at_vis_pos(g, pos);
if (w + pos) > vis_pos {
let d1 = vis_pos - pos;
let d2 = (pos + w) - vis_pos;
if d2 < d1 {
i += 1;
}
break;
}
else {
pos += w;
i += 1;
}
}
else {
break;
}
}
return i;
}
/// Returns an immutable string slice into the text block's memory /// Returns an immutable string slice into the text block's memory
pub fn as_str<'a>(&'a self) -> &'a str { pub fn as_str<'a>(&'a self) -> &'a str {
unsafe { unsafe {
@ -828,6 +878,34 @@ fn text_line_split_beginning() {
assert!(tl2.ending == LineEnding::CRLF); assert!(tl2.ending == LineEnding::CRLF);
} }
#[test]
fn grapheme_index_to_closest_vis_pos_1() {
let tl = Line::new_from_str("Hello!");
assert!(tl.grapheme_index_to_closest_vis_pos(0) == 0);
}
#[test]
fn grapheme_index_to_closest_vis_pos_2() {
let tl = Line::new_from_str("\tHello!");
assert!(tl.grapheme_index_to_closest_vis_pos(1) == TAB_WIDTH);
}
#[test]
fn vis_pos_to_closest_grapheme_index_1() {
let tl = Line::new_from_str("Hello!");
assert!(tl.vis_pos_to_closest_grapheme_index(0) == 0);
}
#[test]
fn vis_pos_to_closest_grapheme_index_2() {
let tl = Line::new_from_str("\tHello!");
assert!(tl.vis_pos_to_closest_grapheme_index(TAB_WIDTH) == 1);
}
//========================================================================= //=========================================================================
// LineGraphemeIter tests // LineGraphemeIter tests

View File

@ -90,11 +90,30 @@ impl Buffer {
} }
pub fn pos_vis_2d_to_closest_1d(&self, pos: (uint, uint)) -> uint {
if pos.0 >= self.line_count() {
return self.len();
}
else {
let gs = self.pos_2d_to_closest_1d((pos.0, 0));
let h = self.get_line(pos.0).vis_pos_to_closest_grapheme_index(pos.1);
return gs + h;
}
}
pub fn pos_1d_to_closest_2d(&self, pos: uint) -> (uint, uint) { pub fn pos_1d_to_closest_2d(&self, pos: uint) -> (uint, uint) {
return self.root.pos_1d_to_closest_2d_recursive(pos); return self.root.pos_1d_to_closest_2d_recursive(pos);
} }
pub fn pos_1d_to_closest_vis_2d(&self, pos: uint) -> (uint, uint) {
let (v, h) = self.root.pos_1d_to_closest_2d_recursive(pos);
let vis_h = self.get_line(v).grapheme_index_to_closest_vis_pos(h);
return (v, vis_h);
}
/// Insert 'text' at grapheme position 'pos'. /// Insert 'text' at grapheme position 'pos'.
pub fn insert_text(&mut self, text: &str, pos: uint) { pub fn insert_text(&mut self, text: &str, pos: uint) {
self.root.insert_text(text, pos); self.root.insert_text(text, pos);

View File

@ -6,6 +6,32 @@ use files::{load_file_to_buffer, save_buffer_to_file};
use string_utils::grapheme_count; use string_utils::grapheme_count;
/// A text cursor. Also represents selections when range.0 != range.1.
///
/// `range` is a pair of 1d grapheme indexes into the text.
///
/// `vis_start` is the visual 2d horizontal position of the cursor. This
/// doesn't affect editing operations at all, but is used for cursor movement.
pub struct Cursor {
pub range: (uint, uint), // start, end
pub vis_start: uint, // start
}
impl Cursor {
pub fn new() -> Cursor {
Cursor {
range: (0, 0),
vis_start: 0,
}
}
pub fn update_vis_start(&mut self, buf: &Buffer) {
let (v, h) = buf.pos_1d_to_closest_2d(self.range.0);
self.vis_start = buf.get_line(v).grapheme_index_to_closest_vis_pos(h);
}
}
pub struct Editor { pub struct Editor {
pub buffer: Buffer, pub buffer: Buffer,
pub file_path: Path, pub file_path: Path,
@ -16,7 +42,7 @@ pub struct Editor {
pub view_pos: (uint, uint), // (line, col) pub view_pos: (uint, uint), // (line, col)
// The editing cursor position // The editing cursor position
pub cursor: (uint, uint), // (line, col) pub cursor: Cursor,
} }
@ -29,7 +55,7 @@ impl Editor {
dirty: false, dirty: false,
view_dim: (0, 0), view_dim: (0, 0),
view_pos: (0, 0), view_pos: (0, 0),
cursor: (0, 0), cursor: Cursor::new(),
} }
} }
@ -42,7 +68,7 @@ impl Editor {
dirty: false, dirty: false,
view_dim: (0, 0), view_dim: (0, 0),
view_pos: (0, 0), view_pos: (0, 0),
cursor: (0, 0), cursor: Cursor::new(),
} }
} }
@ -60,35 +86,38 @@ impl Editor {
/// Moves the editor's view the minimum amount to show the cursor /// Moves the editor's view the minimum amount to show the cursor
pub fn move_view_to_cursor(&mut self) { pub fn move_view_to_cursor(&mut self) {
let (v, h) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0);
// Horizontal // Horizontal
if self.cursor.1 < self.view_pos.1 { if h < self.view_pos.1 {
self.view_pos.1 = self.cursor.1; self.view_pos.1 = h;
} }
else if self.cursor.1 >= (self.view_pos.1 + self.view_dim.1) { else if h >= (self.view_pos.1 + self.view_dim.1) {
self.view_pos.1 = 1 + self.cursor.1 - self.view_dim.1; self.view_pos.1 = 1 + h - self.view_dim.1;
} }
// Vertical // Vertical
if self.cursor.0 < self.view_pos.0 { if v < self.view_pos.0 {
self.view_pos.0 = self.cursor.0; self.view_pos.0 = v;
} }
else if self.cursor.0 >= (self.view_pos.0 + self.view_dim.0) { else if v >= (self.view_pos.0 + self.view_dim.0) {
self.view_pos.0 = 1 + self.cursor.0 - self.view_dim.0; self.view_pos.0 = 1 + v - self.view_dim.0;
} }
} }
pub fn insert_text_at_cursor(&mut self, text: &str) { pub fn insert_text_at_cursor(&mut self, text: &str) {
let pos = self.buffer.pos_2d_to_closest_1d(self.cursor);
let str_len = grapheme_count(text); let str_len = grapheme_count(text);
let p = self.buffer.pos_2d_to_closest_1d(self.cursor);
// Insert text // Insert text
self.buffer.insert_text(text, pos); self.buffer.insert_text(text, self.cursor.range.0);
self.dirty = true; self.dirty = true;
// Move cursor // Move cursor
self.cursor = self.buffer.pos_1d_to_closest_2d(p + str_len); self.cursor.range.0 += str_len;
self.cursor.range.1 += str_len;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
@ -99,67 +128,127 @@ impl Editor {
} }
pub fn remove_text_behind_cursor(&mut self, grapheme_count: uint) { pub fn remove_text_behind_cursor(&mut self, grapheme_count: uint) {
let pos_b = self.buffer.pos_2d_to_closest_1d(self.cursor); let pos_b = self.cursor.range.0;
let pos_a = if pos_b >= grapheme_count {pos_b - grapheme_count} else {0}; let pos_a = if pos_b >= grapheme_count {pos_b - grapheme_count} else {0};
let tot_g = pos_b - pos_a;
// Move cursor
self.cursor = self.buffer.pos_1d_to_closest_2d(pos_a);
// Remove text // Remove text
self.buffer.remove_text(pos_a, pos_b); self.buffer.remove_text(pos_a, pos_b);
self.dirty = true; self.dirty = true;
// Move cursor
self.cursor.range.0 -= tot_g;
self.cursor.range.1 -= tot_g;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: uint) {
let pos_a = self.cursor.range.1;
let pos_b = if (pos_a + grapheme_count) <= self.buffer.len() {pos_a + grapheme_count} else {self.buffer.len()};
// Remove text
self.buffer.remove_text(pos_a, pos_b);
self.dirty = true;
// Move cursor
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_inside_cursor(&mut self) {
// If selection, remove text
if self.cursor.range.0 < self.cursor.range.1 {
self.buffer.remove_text(self.cursor.range.0, self.cursor.range.1);
self.dirty = true;
}
// Move cursor
self.cursor.range.1 = self.cursor.range.0;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_to_beginning_of_buffer(&mut self) { pub fn cursor_to_beginning_of_buffer(&mut self) {
self.cursor = (0, 0); self.cursor.range = (0, 0);
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor();
} }
pub fn cursor_to_end_of_buffer(&mut self) { pub fn cursor_to_end_of_buffer(&mut self) {
self.cursor = self.buffer.pos_1d_to_closest_2d(self.buffer.len()+1); let end = self.buffer.len();
self.cursor.range = (end, end);
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor();
} }
pub fn cursor_left(&mut self) { pub fn cursor_left(&mut self, n: uint) {
let p = self.buffer.pos_2d_to_closest_1d(self.cursor); if self.cursor.range.0 >= n {
self.cursor.range.0 -= n;
if p > 0 {
self.cursor = self.buffer.pos_1d_to_closest_2d(p - 1);
} }
else { else {
self.cursor = self.buffer.pos_1d_to_closest_2d(0); self.cursor.range.0 = 0;
} }
self.cursor.range.1 = self.cursor.range.0;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_right(&mut self) { pub fn cursor_right(&mut self, n: uint) {
let p = self.buffer.pos_2d_to_closest_1d(self.cursor); if self.cursor.range.1 <= (self.buffer.len() - n) {
self.cursor = self.buffer.pos_1d_to_closest_2d(p + 1); self.cursor.range.1 += n;
}
else {
self.cursor.range.1 = self.buffer.len();
}
self.cursor.range.0 = self.cursor.range.1;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_up(&mut self) { pub fn cursor_up(&mut self, n: uint) {
if self.cursor.0 > 0 { let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0);
self.cursor.0 -= 1;
if v >= n {
self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v - n, self.cursor.vis_start));
self.cursor.range.1 = self.cursor.range.0;
} }
else { else {
self.cursor_to_beginning_of_buffer(); self.cursor_to_beginning_of_buffer();
} }
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_down(&mut self) { pub fn cursor_down(&mut self, n: uint) {
if self.cursor.0 < (self.buffer.line_count() - 1) { let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0);
self.cursor.0 += 1;
if v < (self.buffer.line_count() - n) {
self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v + n, self.cursor.vis_start));
self.cursor.range.1 = self.cursor.range.0;
} }
else { else {
self.cursor_to_end_of_buffer(); self.cursor_to_end_of_buffer();
} }
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
@ -167,35 +256,29 @@ impl Editor {
if self.view_pos.0 > 0 { if self.view_pos.0 > 0 {
let move_amount = self.view_dim.0 - (self.view_dim.0 / 8); let move_amount = self.view_dim.0 - (self.view_dim.0 / 8);
if self.view_pos.0 >= move_amount { if self.view_pos.0 >= move_amount {
if self.cursor.0 >= move_amount {
self.cursor.0 -= move_amount;
}
self.view_pos.0 -= move_amount; self.view_pos.0 -= move_amount;
} }
else { else {
if self.cursor.0 >= self.view_pos.0 {
self.cursor.0 -= self.view_pos.0;
}
else {
self.cursor_to_beginning_of_buffer();
}
self.view_pos.0 = 0; self.view_pos.0 = 0;
} }
self.cursor_up(move_amount);
} }
else { else {
self.cursor_to_beginning_of_buffer(); self.cursor_to_beginning_of_buffer();
} }
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn page_down(&mut self) { pub fn page_down(&mut self) {
// TODO
let nlc = self.buffer.line_count() - 1; let nlc = self.buffer.line_count() - 1;
if self.view_pos.0 < nlc { if self.view_pos.0 < nlc {
let move_amount = self.view_dim.0 - (self.view_dim.0 / 8); let move_amount = self.view_dim.0 - (self.view_dim.0 / 8);
let max_move = nlc - self.view_pos.0; let max_move = nlc - self.view_pos.0;
let cursor_max_move = nlc - self.cursor.0;
if max_move >= move_amount { if max_move >= move_amount {
self.view_pos.0 += move_amount; self.view_pos.0 += move_amount;
@ -204,14 +287,13 @@ impl Editor {
self.view_pos.0 += max_move; self.view_pos.0 += max_move;
} }
if cursor_max_move >= move_amount { self.cursor_down(move_amount);
self.cursor.0 += move_amount; }
} else {
else { self.cursor_to_end_of_buffer();
self.cursor_to_end_of_buffer();
}
} }
// Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
} }

View File

@ -87,19 +87,19 @@ impl TermUI {
}, },
K_UP => { K_UP => {
self.editor.cursor_up(); self.editor.cursor_up(1);
}, },
K_DOWN => { K_DOWN => {
self.editor.cursor_down(); self.editor.cursor_down(1);
}, },
K_LEFT => { K_LEFT => {
self.editor.cursor_left(); self.editor.cursor_left(1);
}, },
K_RIGHT => { K_RIGHT => {
self.editor.cursor_right(); self.editor.cursor_right(1);
}, },
K_ENTER => { K_ENTER => {
@ -151,6 +151,7 @@ impl TermUI {
} }
} }
pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) { pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) {
let mut line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0); let mut line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0);
@ -163,8 +164,7 @@ impl TermUI {
let max_print_line = c2.0 - c1.0; let max_print_line = c2.0 - c1.0;
let max_print_col = c2.1 - c1.1; let max_print_col = c2.1 - c1.1;
let cursor_pos_1d = editor.buffer.pos_2d_to_closest_1d(editor.cursor); let cursor_pos = editor.buffer.pos_1d_to_closest_2d(editor.cursor.range.0);
let cursor_pos = editor.buffer.pos_1d_to_closest_2d(cursor_pos_1d);
let print_cursor_pos = (cursor_pos.0 + editor.view_pos.0, cursor_pos.1 + editor.view_pos.1); let print_cursor_pos = (cursor_pos.0 + editor.view_pos.0, cursor_pos.1 + editor.view_pos.1);
loop { loop {
@ -207,7 +207,7 @@ impl TermUI {
} }
} }
else if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 < c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 < c2.1 { else if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 < c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 < c2.1 {
if cursor_pos_1d >= editor.buffer.len() { if editor.cursor.range.0 >= editor.buffer.len() {
self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " "); self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
} }
break; break;

View File

@ -1,6 +1,3 @@
- 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 - Line number display
- Editor info display (filename, current line/column, indentation style, etc.) - Editor info display (filename, current line/column, indentation style, etc.)
- File opening by entering path - File opening by entering path