diff --git a/src/buffer/line.rs b/src/buffer/line.rs index 0f84e8d..67e969c 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -9,10 +9,10 @@ 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 { +fn grapheme_vis_width_at_vis_pos(g: &str, pos: uint, tab_width: uint) -> uint { match g { "\t" => { - let ending_pos = ((pos / TAB_WIDTH) + 1) * TAB_WIDTH; + let ending_pos = ((pos / tab_width) + 1) * tab_width; return ending_pos - pos; }, @@ -233,11 +233,11 @@ impl Line { /// Returns the visual cell width of the line - pub fn vis_width(&self) -> uint { + pub fn vis_width(&self, tab_width: uint) -> uint { let mut width = 0; for g in self.as_str().graphemes(true) { - let w = grapheme_vis_width_at_vis_pos(g, width); + let w = grapheme_vis_width_at_vis_pos(g, width, tab_width); width += w; } @@ -263,8 +263,8 @@ impl Line { } - pub fn grapheme_width_at_index(&self, index: uint) -> uint { - let mut iter = self.grapheme_vis_iter(); + pub fn grapheme_width_at_index(&self, index: uint, tab_width: uint) -> uint { + let mut iter = self.grapheme_vis_iter(tab_width); let mut i = 0; for (_, _, width) in iter { @@ -282,13 +282,13 @@ impl Line { /// Translates a grapheme index into a visual horizontal position - pub fn grapheme_index_to_closest_vis_pos(&self, index: uint) -> uint { + pub fn grapheme_index_to_closest_vis_pos(&self, index: uint, tab_width: 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); + let w = grapheme_vis_width_at_vis_pos(g, pos, tab_width); pos += w; } else { @@ -301,14 +301,14 @@ impl Line { /// Translates a visual horizontal position to the closest grapheme index - pub fn vis_pos_to_closest_grapheme_index(&self, vis_pos: uint) -> uint { + pub fn vis_pos_to_closest_grapheme_index(&self, vis_pos: uint, tab_width: 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); + let w = grapheme_vis_width_at_vis_pos(g, pos, tab_width); if (w + pos) > vis_pos { let d1 = vis_pos - pos; let d2 = (pos + w) - vis_pos; @@ -483,10 +483,11 @@ impl Line { /// Returns an iterator over the graphemes of the line - pub fn grapheme_vis_iter<'a>(&'a self) -> LineGraphemeVisIter<'a> { + pub fn grapheme_vis_iter<'a>(&'a self, tab_width: uint) -> LineGraphemeVisIter<'a> { LineGraphemeVisIter { graphemes: self.grapheme_iter(), vis_pos: 0, + tab_width: tab_width, } } } @@ -629,6 +630,7 @@ impl<'a> Iterator<&'a str> for LineGraphemeIter<'a> { pub struct LineGraphemeVisIter<'a> { graphemes: LineGraphemeIter<'a>, vis_pos: uint, + tab_width: uint, } impl<'a> LineGraphemeVisIter<'a> { @@ -666,7 +668,7 @@ 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); + let width = grapheme_vis_width_at_vis_pos(g, self.vis_pos, self.tab_width); self.vis_pos += width; return Some((g, pos, width)); } diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index cdc8786..8abdd0b 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -50,12 +50,12 @@ impl Buffer { } - pub fn get_grapheme_width(&self, index: uint) -> uint { + pub fn get_grapheme_width(&self, index: uint, tab_width: uint) -> uint { if index >= self.len() { panic!("Buffer::get_grapheme_width(): index past last grapheme."); } else { - return self.text.get_grapheme_width_recursive(index); + return self.text.get_grapheme_width_recursive(index, tab_width); } } @@ -112,13 +112,13 @@ impl Buffer { } - pub fn pos_vis_2d_to_closest_1d(&self, pos: (uint, uint)) -> uint { + pub fn pos_vis_2d_to_closest_1d(&self, pos: (uint, uint), tab_width: 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); + let h = self.get_line(pos.0).vis_pos_to_closest_grapheme_index(pos.1, tab_width); return gs + h; } } @@ -129,9 +129,9 @@ impl Buffer { } - pub fn pos_1d_to_closest_vis_2d(&self, pos: uint) -> (uint, uint) { + pub fn pos_1d_to_closest_vis_2d(&self, pos: uint, tab_width: uint) -> (uint, uint) { let (v, h) = self.text.pos_1d_to_closest_2d_recursive(pos); - let vis_h = self.get_line(v).grapheme_index_to_closest_vis_pos(h); + let vis_h = self.get_line(v).grapheme_index_to_closest_vis_pos(h, tab_width); return (v, vis_h); } diff --git a/src/buffer/node.rs b/src/buffer/node.rs index 2106f36..6d1b85b 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -202,18 +202,18 @@ impl BufferNode { } - pub fn get_grapheme_width_recursive(&self, index: uint) -> uint { + pub fn get_grapheme_width_recursive(&self, index: uint, tab_width: uint) -> uint { match self.data { BufferNodeData::Leaf(ref line) => { - return line.grapheme_width_at_index(index); + return line.grapheme_width_at_index(index, tab_width); }, BufferNodeData::Branch(ref left, ref right) => { if index < left.grapheme_count { - return left.get_grapheme_width_recursive(index); + return left.get_grapheme_width_recursive(index, tab_width); } else { - return right.get_grapheme_width_recursive(index - left.grapheme_count); + return right.get_grapheme_width_recursive(index - left.grapheme_count, tab_width); } } } diff --git a/src/editor.rs b/src/editor.rs index 3eee76e..9f96183 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,6 +2,7 @@ use buffer::Buffer; use std::path::Path; +use std::cmp::min; use files::{load_file_to_buffer, save_buffer_to_file}; use string_utils::grapheme_count; @@ -25,9 +26,9 @@ impl Cursor { } } - pub fn update_vis_start(&mut self, buf: &Buffer) { + pub fn update_vis_start(&mut self, buf: &Buffer, tab_width: uint) { 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); + self.vis_start = buf.get_line(v).grapheme_index_to_closest_vis_pos(h, tab_width); } } @@ -35,6 +36,8 @@ impl Cursor { pub struct Editor { pub buffer: Buffer, pub file_path: Path, + pub soft_tabs: bool, + pub tab_width: uint, pub dirty: bool, // The dimensions and position of the editor's view within the buffer @@ -52,6 +55,8 @@ impl Editor { Editor { buffer: Buffer::new(), file_path: Path::new(""), + soft_tabs: false, + tab_width: 4, dirty: false, view_dim: (0, 0), view_pos: (0, 0), @@ -62,14 +67,20 @@ impl Editor { pub fn new_from_file(path: &Path) -> Editor { let buf = load_file_to_buffer(path).unwrap(); - Editor { + let mut ed = Editor { buffer: buf, file_path: path.clone(), + soft_tabs: false, + tab_width: 4, dirty: false, view_dim: (0, 0), view_pos: (0, 0), cursor: Cursor::new(), - } + }; + + ed.auto_detect_indentation_style(); + + return ed; } pub fn save_if_dirty(&mut self) { @@ -79,6 +90,100 @@ impl Editor { } } + pub fn auto_detect_indentation_style(&mut self) { + let mut tab_blocks: uint = 0; + let mut space_blocks: uint = 0; + let mut space_histogram: [uint, ..9] = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + + let mut last_indent = (false, 0u); // (was_tabs, indent_count) + + // Collect statistics + let mut line_i: uint = 0; + for line in self.buffer.line_iter() { + let mut g_iter = line.grapheme_iter(); + match g_iter.next() { + Some("\t") => { + // Count leading tabs + let mut count = 1; + for g in g_iter { + if g == "\t" { + count += 1; + } + else { + break; + } + } + + // Update stats + if last_indent.0 && last_indent.1 < count { + tab_blocks += 1; + } + + // Store last line info + last_indent = (true, count); + }, + + Some(" ") => { + // Count leading spaces + let mut count = 1; + for g in g_iter { + if g == " " { + count += 1; + } + else { + break; + } + } + + // Update stats + if !last_indent.0 && last_indent.1 < count { + space_blocks += 1; + let amount = count - last_indent.1; + if amount < 9 { + space_histogram[amount] += 1; + } + else { + space_histogram[8] += 1; + } + } + + // Store last line info + last_indent = (false, count); + }, + + _ => {}, + } + + // Stop after 1000 lines + line_i += 1; + if line_i > 1000 { + break; + } + } + + // Analyze stats and make a determination + if space_blocks == 0 && tab_blocks == 0 { + return; + } + + if space_blocks > (tab_blocks * 2) { + let mut width = 0; + let mut width_count = 0; + for i in range(0, 9) { + if space_histogram[i] > width_count { + width = i; + width_count = space_histogram[i]; + } + } + + self.soft_tabs = true; + self.tab_width = width; + } + else { + self.soft_tabs = false; + } + } + pub fn update_dim(&mut self, h: uint, w: uint) { self.view_dim = (h, w); } @@ -86,7 +191,7 @@ impl Editor { /// Moves the editor's view the minimum amount to show the cursor pub fn move_view_to_cursor(&mut self) { - let (v, h) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0); + let (v, h) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0, self.tab_width); // Horizontal if h < self.view_pos.1 { @@ -115,12 +220,43 @@ impl Editor { // Move cursor self.cursor.range.0 += str_len; self.cursor.range.1 += str_len; - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); } + pub fn insert_tab_at_cursor(&mut self) { + if self.soft_tabs { + // Figure out how many spaces to insert + let (v, h) = self.buffer.pos_1d_to_closest_2d(self.cursor.range.0); + let vis_pos = self.buffer.get_line(v).grapheme_index_to_closest_vis_pos(h, self.tab_width); + let next_tab_stop = ((vis_pos / self.tab_width) + 1) * self.tab_width; + let space_count = min(next_tab_stop - vis_pos, 8); + + + // Insert spaces + let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "]; + self.buffer.insert_text(space_strs[space_count], self.cursor.range.0); + self.dirty = true; + + // Move cursor + self.cursor.range.0 += space_count; + self.cursor.range.1 += space_count; + self.cursor.update_vis_start(&(self.buffer), self.tab_width); + + // Adjust view + self.move_view_to_cursor(); + } + else { + self.insert_text_at_cursor("\t"); + } + } + + pub fn backspace_at_cursor(&mut self) { + self.remove_text_behind_cursor(1); + } + pub fn insert_text_at_grapheme(&mut self, text: &str, pos: uint) { self.dirty = true; let buf_len = self.buffer.len(); @@ -139,7 +275,7 @@ impl Editor { // Move cursor self.cursor.range.0 -= tot_g; self.cursor.range.1 -= tot_g; - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -154,7 +290,7 @@ impl Editor { self.dirty = true; // Move cursor - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -169,7 +305,7 @@ impl Editor { // Move cursor self.cursor.range.1 = self.cursor.range.0; - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -177,7 +313,7 @@ impl Editor { pub fn cursor_to_beginning_of_buffer(&mut self) { self.cursor.range = (0, 0); - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -186,7 +322,7 @@ impl Editor { pub fn cursor_to_end_of_buffer(&mut self) { let end = self.buffer.len(); self.cursor.range = (end, end); - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -201,7 +337,7 @@ impl Editor { } self.cursor.range.1 = self.cursor.range.0; - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); @@ -216,17 +352,17 @@ impl Editor { } self.cursor.range.0 = self.cursor.range.1; - self.cursor.update_vis_start(&(self.buffer)); + self.cursor.update_vis_start(&(self.buffer), self.tab_width); // Adjust view self.move_view_to_cursor(); } pub fn cursor_up(&mut self, n: uint) { - let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0); + let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0, self.tab_width); if v >= n { - self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v - n, self.cursor.vis_start)); + self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v - n, self.cursor.vis_start), self.tab_width); self.cursor.range.1 = self.cursor.range.0; } else { @@ -238,10 +374,10 @@ impl Editor { } pub fn cursor_down(&mut self, n: uint) { - let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0); + let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(self.cursor.range.0, self.tab_width); 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.0 = self.buffer.pos_vis_2d_to_closest_1d((v + n, self.cursor.vis_start), self.tab_width); self.cursor.range.1 = self.cursor.range.0; } else { @@ -299,8 +435,8 @@ impl Editor { pub fn jump_to_line(&mut self, n: uint) { let pos = self.buffer.pos_2d_to_closest_1d((n, 0)); - let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(pos); - self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v, self.cursor.vis_start)); + let (v, _) = self.buffer.pos_1d_to_closest_vis_2d(pos, self.tab_width); + self.cursor.range.0 = self.buffer.pos_vis_2d_to_closest_1d((v, self.cursor.vis_start), self.tab_width); self.cursor.range.1 = self.cursor.range.0; // Adjust view diff --git a/src/term_ui.rs b/src/term_ui.rs index 8daaaca..ac68a45 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -136,11 +136,11 @@ impl TermUI { }, K_TAB => { - self.editor.insert_text_at_cursor("\t"); + self.editor.insert_tab_at_cursor(); }, K_BACKSPACE => { - self.editor.remove_text_behind_cursor(1); + self.editor.backspace_at_cursor(); }, K_DELETE => { @@ -312,7 +312,8 @@ impl TermUI { LineEnding::LS => "LS", LineEnding::PS => "PS", }; - let info_line = format!("UTF8:{} tabs:4", nl); + let soft_tabs_str = if editor.soft_tabs {"spaces"} else {"tabs"}; + let info_line = format!("UTF8:{} {}:{}", nl, soft_tabs_str, editor.tab_width); self.rb.print(c2.1 - 30, c1.0, rustbox::RB_NORMAL, foreground, background, info_line.as_slice()); // Draw main text editing area @@ -336,13 +337,13 @@ impl TermUI { loop { if let Some(line) = line_iter.next() { - let mut g_iter = line.grapheme_vis_iter(); + let mut g_iter = line.grapheme_vis_iter(editor.tab_width); let excess = g_iter.skip_vis_positions(editor.view_pos.1); vis_col_num += excess; print_col_num += excess; - grapheme_index = editor.buffer.pos_vis_2d_to_closest_1d((vis_line_num, vis_col_num)); + grapheme_index = editor.buffer.pos_vis_2d_to_closest_1d((vis_line_num, vis_col_num), editor.tab_width); for (g, pos, width) in g_iter { print_col_num = pos - editor.view_pos.1; @@ -395,7 +396,7 @@ impl TermUI { // Print cursor if it's at the end of the text, and thus wasn't printed // already. if editor.cursor.range.0 >= editor.buffer.len() { - let vis_cursor_pos = editor.buffer.pos_1d_to_closest_vis_2d(editor.cursor.range.0); + let vis_cursor_pos = editor.buffer.pos_1d_to_closest_vis_2d(editor.cursor.range.0, editor.tab_width); if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) { let print_cursor_pos = (vis_cursor_pos.0 - editor.view_pos.0 + c1.0, vis_cursor_pos.1 - editor.view_pos.1 + c1.1);