Soft tabs and user-settable tab-widths are now supported.

Also, indentation style is automatically detected on file load.
This commit is contained in:
Nathan Vegdahl 2015-01-04 00:09:34 -08:00
parent 0941339906
commit ff6c763821
5 changed files with 186 additions and 47 deletions

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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);