gchp / iota

A terminal-based text editor written in Rust
MIT License
1.63k stars 81 forks source link

Can't delete from the end of the buffer #82

Closed gchp closed 9 years ago

gchp commented 9 years ago

When you try to delete backwards from the end of a buffer, Iota will crash with the following error:

thread '<main>' panicked at 'index out of range', /home/gchp/.cargo/git/checkouts/gapbuffer-8b2fd52819acbe54/master/src/lib.rs:271
stack backtrace:
   1:     0x7f19824fccd0 - sys::backtrace::write::hb7fa0f4f6b33ee3a8Rt
   2:     0x7f19825001e0 - failure::on_fail::h4388493538a5ad8ck8z
   3:     0x7f19824e90d0 - rt::unwind::begin_unwind_inner::h644ddf1c409df284cNz
   4:     0x7f19824aea60 - rt::unwind::begin_unwind::h12738154783134983540
   5:     0x7f19824c2170 - GapBuffer<T>::remove::h18422877085795087137
   6:     0x7f19824c17b0 - buffer::Buffer::remove_chars::unboxed_closure.4893
   7:     0x7f19824c16a0 - iter::FilterMap<A, B, I, F>.Iterator::next::h14431122262248638431
   8:     0x7f19824c1620 - iter::Inspect<A, I, F>.Iterator::next::h16128471622825949221
   9:     0x7f19824c15a0 - iter::Map<A, B, I, F>.Iterator::next::h10824938953637993840
  10:     0x7f19824c12f0 - vec::Vec<T>.FromIterator<T>::from_iter::h5751581531197774384
  11:     0x7f19824c1250 - iter::IteratorExt::collect::h17492329597111519392
  12:     0x7f19824c0d00 - buffer::Buffer::remove_chars::h99717a5c0fab4d4brKa
  13:     0x7f19824ccdd0 - view::View<'v>::delete_chars::h077bd7f050c8e641qUb
  14:     0x7f19823f9090 - editor::Editor<'e, T>::handle_command::h9268632214995713731
  15:     0x7f19823f38f0 - editor::Editor<'e, T>::handle_key_event::h9162057277965722021
  16:     0x7f19823f0540 - editor::Editor<'e, T>::start::h10259774018777455307
  17:     0x7f19823e04a0 - main::hf912c98fa0bf880cJda
  18:     0x7f1982504e20 - rust_try_inner
  19:     0x7f1982504e10 - rust_try
  20:     0x7f1982501da0 - rt::lang_start::h46417f3fa3eb30a5w2z
  21:     0x7f19823e0b50 - main
  22:     0x7f19815e7a50 - __libc_start_main
  23:     0x7f19823e0000 - <unknown>
  24:                0x0 - <unknown>
An unknown error occurred

Seems as if the cursor position is one greater than the length of the buffer.

SebastianKeller commented 9 years ago

Couldn't really debug this, but i have a guess:

As the last char gets removed, the mark is is at text.len(). As of this, get_line_end will return None, and shift_mark will refuse to alter the marks position. The crash will happen on the next backspace, probably because the mark is outside of text.

pub fn delete_chars(&mut self, direction: Direction, num_chars: usize) {
    let chars = self.buffer.remove_chars(self.cursor, direction, num_chars);
    // mark is now outside of the text

    match (chars, direction) {
        (Some(chars), Direction::Left) => {
            self.move_cursor(Direction::Left, chars.len());
        }
        (Some(chars), Direction::LeftWord(..)) => {
            self.move_cursor(Direction::Left, chars.len());
        }
        _ => {}
    }
}

/// Shift a mark relative to its position according to the direction given.
pub fn shift_mark(&mut self, mark: Mark, direction: Direction, amount: usize) {
    let last = self.len() - 1;
    let text = &self.text;
    if let Some(tuple) = self.marks.get_mut(&mark) {
        let (idx, line_idx) = *tuple;
        /**
        * get_line_end will return None
        */
        if let (Some(line), Some(line_end)) = (get_line(idx, text), get_line_end(idx, text)) { 
            *tuple = match direction {
                //For every relative motion of a mark, should return this tuple:
                //  value 0:    the absolute index of the mark in the file
                //  value 1:    the index of the mark in its line (unchanged by direct verticle
                //              traversals)
                Direction::Left =>  {
                    let n = amount;
                    if idx >= n { (idx - n, idx - n - get_line(idx - n, text).unwrap()) }
                    else { (0, 0) }
                }
....
/// Returns the index of the newline character at the end of the line mark is in.
/// Newline after mark (INCLUSIVE).
/// None if mark is outside the len of text.
fn get_line_end(mark: usize, text: &GapBuffer<u8>) -> Option<usize> {
    if mark <= text.len() { // <-- never true in our case
        range(mark, text.len() + 1).filter(|idx| *idx == text.len() ||text[*idx] == b'\n')
                                   .take(1)
                                   .next()
    } else { None }
}
SebastianKeller commented 9 years ago

Please, see this PR https://github.com/gchp/iota/pull/86