On 10/26/07, Ruby Quiz <james / grayproductions.net> wrote:
>         http://en.wikipedia.org/wiki/Gap_buffer

Here is a strange solution of mine.

I start by partially implementing a string class that works equally
well on the front and the back (double ended queue string).  My
array.c patch from a couple years ago did something like this, but the
data structure below is a bit more memory efficient since it can share
a single "gap" and recirculates unused space better.  Basically, the
implementation below is a circular buffer that reallocates when it is
full.  It would be great if the standard String and Array were
symmetrical like this.  It would increase the ways to use simple
strings and arrays.

Next I use this double ended queue string to implement something like
a gap buffer.  You can think of the gap as being at the ends of of
this double-ended string.  Text before the cursor is at the end of the
string and text before the cursor is at the beginning.

The performance is O(n), but not nearly as fast as some a simple
solution (5X slower).  If String was symmetical, I would use that and
it would be a different story.

If I have time, I may try this DQString in a linked-list data-structure...

dqstring.rb:


# characters can be in either of these ranges:
#     gapped : @begin...length, 0...@end
#     contiguous : @begin...@end
# inheriting String only for memory efficiency
# inherited methods won't necessarily make sense

class DQString < String
    def initialize(data="")
        @begin = 0
        @end = data.length
        super(data)
    end
    def inspect
        "#<DQString: begin=#{@begin}, end=#{@end}, #{super}>"
    end
    def length
        if (len = @end-@begin)>=0
            len
        else
            super+len
        end
    end
    def << (ch)
        if @end==size
            if @begin>1
                # use the front, making a gap in the middle
                self[0] = ch
                @end = 1
                self
            else
                @end += 1
                # let ruby realloc when needed
                super(ch)
            end
        else
            self[@end] = ch
            @end += 1
            if @end==@begin
                # double the size when the gap becomes empty
                @begin += size
                self.replace(self+self)
            else
                self
            end
        end
    end
    def >> (ch)
        if @begin.zero?
            @begin = size-1
            if @end<@begin
                # use the back, making a gap in the middle
                self
            else
                # double the size to make room at the front
                @end += size
                self.replace(self+self)
            end
        else
            @begin -= 1
            if @begin==@end
                # double the size when the gap becomes empty
                @begin += size
                self.replace(self+self)
            else
                self
            end
        end
    ensure
        self[@begin] = ch
    end
    def pop
        if @end==@begin
            nil
        else
            @end -= 1
            ch = self[@end]
            if (len = @end-@begin)>=0
                # remove excess trailing space if too much
                self.slice!(-len,len) if size-@end>(len<<1)
            elsif @end.zero?
                len = (@end=size)-@begin
                if @begin>(len<<1)
                    # remove excess leading space
                    self.slice!(0, len)
                    @begin -= len
                    @end -= len
                end
            end
            ch
        end
    end
    def shift
        if @begin==@end
            nil
        else
            ch = self[@begin]
            @begin += 1
            if (len = @end-@begin)>=0
                if @begin>(len<<1)
                    # remove excess leading space
                    self.slice!(0, len)
                    @begin -= len
                    @end -= len
                end
            elsif @begin==size
                # remove excess trailing space if too much
                self.slice!(-@end,@end) if (@end<<1)+len<0
                @begin = 0
            end
            ch
        end
    end
end


dqcursor.rb:


require 'dqstring'

class DQCursor
    def initialize
        @data = DQString.new
        @nafter = 0
    end
    def insert_before(ch)
        @data << ch
    end
    def insert_after(ch)
        @nafter += 1
        @data >> ch
    end
    def delete_before
        @data.length>@nafter and @data.pop
    end
    def delete_after
        @nafter.nonzero? and (@nafter-=1; @data.shift)
    end
    def left
        @data.length>@nafter and (@nafter+=1; @data >> @data.pop)
    end
    def right
        @nafter.nonzero? and (@nafter-=1; @data << @data.shift)
    end
    def up
        nbefore = @data.length-@nafter
        while nbefore.nonzero?
            nbefore -= 1
            @data >> (ch=@data.pop)
            return(true) if ch==?\n
        end
    ensure
        @nafter=@data.length-nbefore
    end
    def down
        while @nafter.nonzero?
            @nafter -= 1
            @data << (ch=@data.shift)
            return(true) if ch==?\n
        end
    end
end