diff --git a/gap_buffer.v b/gap_buffer.v new file mode 100644 index 00000000..89a7cc21 --- /dev/null +++ b/gap_buffer.v @@ -0,0 +1,98 @@ +module buffers + +import arrays + +pub struct GapBuffer { + initial_gap_size u32 +mut: + data []rune + gap_start u32 + gap_end u32 +} + +const null_code_point = rune(0xfeff) +const initial_gap_size = u32(32) + +@[params] +pub struct GapBufferParams { + content []rune + gap_size u32 = initial_gap_size +} + +pub fn GapBuffer.new(opts GapBufferParams) GapBuffer { + mut gb := GapBuffer{ + initial_gap_size: opts.gap_size + data: []rune{ len: opts.content.len + int(opts.gap_size), init: null_code_point } + gap_start: 0 + gap_end: opts.gap_size + } + gb.initial_fill(opts.content) + return gb +} + +fn (mut g GapBuffer) initial_fill(data []rune) { + for i, c in data { + g.data[int(g.gap_end) + i] = c + } +} + +fn (mut g GapBuffer) grow_gap() { + mut dest := []rune{ len: g.data.len + int(g.initial_gap_size), init: null_code_point } + arrays.copy(mut dest[..g.gap_start], g.data[..g.gap_start]) + gap_end := g.gap_start + g.initial_gap_size + arrays.copy(mut dest[gap_end..], g.data[g.gap_end..]) + g.gap_end = gap_end + g.data = dest +} + +fn (mut g GapBuffer) move_gap(position int) { + if position == g.gap_start { + return + } + + if position > g.gap_start { + chars_to_move := position - int(g.gap_start) + for i in 0..chars_to_move { + g.data[int(g.gap_start) + i] = g.data[int(g.gap_end) + i] + g.data[int(g.gap_end) + i] = null_code_point + } + g.gap_start = u32(position) + g.gap_end += u32(chars_to_move) + return + } + + chars_to_move := int(g.gap_start) - position + for i := chars_to_move - 1; i >= 0; i-- { + g.data[int(g.gap_end) - chars_to_move + i] = g.data[position + i] + g.data[position + i] = null_code_point + } + g.gap_end -= u32(chars_to_move) + g.gap_start = u32(position) +} + +fn (g GapBuffer) current_gap_size() u32 { + return u32(g.gap_end - g.gap_start) +} + +pub fn (mut g GapBuffer) insert_char(data rune) { + if g.current_gap_size() == 0 { + g.grow_gap() + } + g.data[g.gap_start] = data + g.gap_start += 1 +} + +pub fn (g GapBuffer) content() string { + pre_gap := g.data[..g.gap_start] + post_gap := g.data[g.gap_end..] + return pre_gap.string() + post_gap.string() +} + +pub fn (g GapBuffer) raw_content() []rune { + return g.data +} + +fn null_code_point_to_str(c rune) rune { + return if c == null_code_point { `_` } else { c } +} + diff --git a/gap_buffer_test.v b/gap_buffer_test.v new file mode 100644 index 00000000..de87786d --- /dev/null +++ b/gap_buffer_test.v @@ -0,0 +1,178 @@ +module buffers + +fn test_initialise_gap_buffer_with_no_contents() { + gb := GapBuffer.new(content: ''.runes()) + assert gb.content() == '' + assert gb.raw_content().map(null_code_point_to_str).string() == `_`.repeat(int(initial_gap_size)) +} + +fn test_initialise_gap_buffer_with_content() { + gb := GapBuffer.new(content: 'abcdef'.runes()) + assert gb.content() == 'abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == '${`_`.repeat(int(initial_gap_size))}abcdef' +} + +fn test_insert_char_into_gap_buffer_with_no_existing_content() { + mut gb := GapBuffer.new(content: ''.runes()) + gb.insert_char(`z`) + assert gb.content() == 'z' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z${`_`.repeat(int(initial_gap_size - 1))}' +} + +fn test_insert_char_into_gap_buffer_with_existing_content() { + mut gb := GapBuffer.new(content: 'abcdef'.runes()) + gb.insert_char(`z`) + assert gb.content() == 'zabcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z${`_`.repeat(int(initial_gap_size - 1))}abcdef' +} + +fn test_insert_char_into_gap_buffer_with_existing_content_with_custom_gap_size() { + mut gb := GapBuffer.new(content: 'abcdef'.runes(), gap_size: 3) + gb.insert_char(`z`) + assert gb.content() == 'zabcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z__abcdef' +} + +@[assert_continues] +fn test_insert_char_into_gap_buffer_with_existing_content_overflow_gap_grows_gap() { + mut gb := GapBuffer.new(content: 'abcdef'.runes(), gap_size: 3) + gb.insert_char(`z`) + assert gb.content() == 'zabcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z__abcdef' + + gb.insert_char(`1`) + assert gb.content() == 'z1abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z1_abcdef' + + gb.insert_char(`2`) + assert gb.content() == 'z12abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z12abcdef' + + gb.insert_char(`3`) + assert gb.content() == 'z123abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z123__abcdef' +} + +@[assert_continues] +fn test_insert_char_into_gap_buffer_with_existing_content_overflow_gap_grows_gap_consistently() { + mut gb := GapBuffer.new(content: 'abcdef'.runes(), gap_size: 3) + gb.insert_char(`z`) + assert gb.content() == 'zabcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z__abcdef' + + gb.insert_char(`1`) + assert gb.content() == 'z1abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z1_abcdef' + + gb.insert_char(`2`) + assert gb.content() == 'z12abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z12abcdef' + + gb.insert_char(`3`) + assert gb.content() == 'z123abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z123__abcdef' + + gb.insert_char(`4`) + assert gb.content() == 'z1234abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z1234_abcdef' + + gb.insert_char(`5`) + assert gb.content() == 'z12345abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z12345abcdef' + + gb.insert_char(`6`) + assert gb.content() == 'z123456abcdef' + assert gb.raw_content().map(null_code_point_to_str).string() == 'z123456__abcdef' +} + +@[assert_continues] +fn test_move_gap_buffer_simplest_case() { + mut gb := GapBuffer.new(content: 'abcdefghijk'.runes(), gap_size: 3) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' + + gb.move_gap(1) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'a___bcdefghijk' +} + +@[assert_continues] +fn test_move_gap_buffer_to_middle() { + mut gb := GapBuffer.new(content: 'abcdefghijk'.runes(), gap_size: 3) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' + + gb.move_gap(5) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcde___fghijk' +} + +@[assert_continues] +fn test_move_gap_buffer_to_middle_and_back() { + mut gb := GapBuffer.new(content: 'abcdefghijk'.runes(), gap_size: 3) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' + + gb.move_gap(5) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcde___fghijk' + + gb.move_gap(0) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' +} + +@[assert_continues] +fn test_move_gap_buffer_to_middle_end_and_back() { + mut gb := GapBuffer.new(content: 'abcdefghijk'.runes(), gap_size: 3) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' + + gb.move_gap(5) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcde___fghijk' + + gb.move_gap(gb.content().runes().len) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcdefghijk___' + + gb.move_gap(5) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcde___fghijk' + + gb.move_gap(0) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' +} + +@[assert_continues] +fn test_move_gap_buffer_to_middle_and_back_alongside_inserts() { + mut gb := GapBuffer.new(content: 'abcdefghijk'.runes(), gap_size: 3) + assert gb.content() == 'abcdefghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '___abcdefghijk' + + gb.move_gap(5) + gb.insert_char(`1`) + assert gb.content() == 'abcde1fghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == 'abcde1__fghijk' + + gb.move_gap(0) + gb.insert_char(`2`) + assert gb.content() == '2abcde1fghijk' + assert gb.raw_content().map(null_code_point_to_str).string() == '2_abcde1fghijk' + + gb.move_gap(gb.content().runes().len) + gb.insert_char(`3`) + assert gb.content() == '2abcde1fghijk3' + assert gb.raw_content().map(null_code_point_to_str).string() == '2abcde1fghijk3' + + gb.insert_char(`4`) + assert gb.content() == '2abcde1fghijk34' + assert gb.raw_content().map(null_code_point_to_str).string() == '2abcde1fghijk34__' + + gb.move_gap(5) + gb.insert_char(`5`) + assert gb.content() == '2abcd5e1fghijk34' + assert gb.raw_content().map(null_code_point_to_str).string() == '2abcd5_e1fghijk34' +} +