My solution is a deque... The text before the cursor is kept
left-to-right, while the part after the cursor is reversed. This makes
for some very simple code for the basic operations, using mostly
push/pop. It might be worthwhile to not reverse the data, and make the
code slightly more assymetric by combining the push/pop with
unshift/shift. (Didn't try that, though...)

Initially, the "left" method was @post.push(@prev.pop), with the
"right" method similar. This proved to be terribly slow, so I kept a
"temporary" cursor that would collapse multiple left/right operations
into a single "sift" (i.e. moving n chars from one to the other). The
"sync" calls ensure that we do that sift before other operations.

The most complex methods (and slowest) here are "up" and "down".
Breaking the internals down into multiple lines rather than just the
@prev/@post pair, or somehow keeping track of the newlines would help
with the speed of up/down, but I got lazy.  =)


class DequeBuffer
	def initialize(data = "", i = 0)
		@prev, @post = data[0, i].unpack("c*"), data[i..-1].unpack("c*").reverse
		@curs = 0
	end

	def insert_before(ch)
		sync
		@prev.push(ch)
	end

	def insert_after(ch)
		sync
		@post.push(ch)
	end

	def delete_before
		sync
		@prev.pop
	end

	def delete_after
		sync
		@post.pop
	end

	def left
		@curs -= 1 if @curs > - / prev.length
	end

	def right
		@curs += 1 if @curs <  @post.length
	end
	
	def up
		sync
		c = @prev.rindex(?\n)
		if c
			c -= @prev.length
			sift(c)					# move to end of prev line
			d = (@prev.rindex(?\n) || -1) - @prev.length - c
			sift(d) if d < 0		# move to column
			true
		end
	end

	def down
		sync
		c = @post.rindex(?\n)
		if c
			c -= @post.length
			sift(-c)					# move to start of next line
			d = (@post.rindex(?\n) || -1) - @post.length - c
			sift(-d) if d < 0		# move to column
			true
		end
	end

	def to_s
		(@prev + @post.reverse).pack("c*")
	end

	private

	def sift(n)
		if n < 0
			@post.concat(@prev.slice!( n, -n).reverse)
		elsif n > 0
			@prev.concat(@post.slice!(-n,  n).reverse)
		end
	end

	def sync
		sift(@curs)
		@curs = 0
	end
end


Running the test using simple string solution as comparison:

ruby -r deque.rb edit_test.rb 1 Simple.new 2000 100 DequeBuffer.new 2000 100
Simple.new: 2000x100
insert_before     0.890000   0.000000   0.890000 (  0.889501)
left              0.170000   0.000000   0.170000 (  0.173849)
right             0.190000   0.000000   0.190000 (  0.184943)
up                0.200000   0.000000   0.200000 (  0.204166)
down              0.210000   0.000000   0.210000 (  0.205926)
insert_after      4.790000   0.010000   4.800000 (  4.802009)
delete_before     3.480000   0.000000   3.480000 (  3.484238)
delete_after      1.760000   0.010000   1.770000 (  1.764394)
total            11.690000   0.020000  11.710000 ( 11.709026)
DequeBuffer.new: 2000x100
insert_before     0.280000   0.000000   0.280000 (  0.285044)
left              0.180000   0.000000   0.180000 (  0.182072)
right             0.160000   0.000000   0.160000 (  0.160996)
up                1.530000   1.850000   3.380000 (  3.373222)
down              0.780000   0.920000   1.700000 (  1.714187)
insert_after      0.290000   0.000000   0.290000 (  0.285273)
delete_before     0.250000   0.000000   0.250000 (  0.251946)
delete_after      0.250000   0.000000   0.250000 (  0.257460)
total             3.720000   2.770000   6.490000 (  6.510201)