Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions gap_buffer.v
Original file line number Diff line number Diff line change
@@ -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 }
}

178 changes: 178 additions & 0 deletions gap_buffer_test.v
Original file line number Diff line number Diff line change
@@ -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'
}