gap_buffer 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +38 -0
- data/Rakefile +71 -0
- data/bench/gap_buffer.rb +40 -0
- data/ext/gap_buffer/extconf.rb +62 -0
- data/ext/gap_buffer/gap_buffer.cpp +480 -0
- data/ext/gap_buffer/gap_buffer.h +159 -0
- data/ext/gap_buffer/rubymain.cpp +191 -0
- data/gap_buffer.gemspec +14 -0
- data/test/test.rb +3 -0
- data/test/test_gap_buffer.rb +140 -0
- metadata +75 -0
data/README
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Implementation of a Gap Buffer for Ruby MRI.
|
2
|
+
(c) 2010 Lourens Naudé (methodmissing), based off work from Hsin Tsao <stsao@lazyhacker.com>
|
3
|
+
|
4
|
+
http://github.com/methodmissing/gap_buffer
|
5
|
+
|
6
|
+
How Gap Buffers work :
|
7
|
+
|
8
|
+
http://en.wikipedia.org/wiki/Gap_buffer
|
9
|
+
http://www.lazyhacker.com/gapbuffer/gapbuffer.htm
|
10
|
+
|
11
|
+
This library works with Ruby 1.8 and 1.9 and exposes the following API :
|
12
|
+
|
13
|
+
gb = GapBuffer.new
|
14
|
+
gb.size #=> 20
|
15
|
+
gb.offset #=> 9
|
16
|
+
gb << "test"
|
17
|
+
gb.print #=> test_______________
|
18
|
+
gb.insert('b')
|
19
|
+
gb.offset #=> 5
|
20
|
+
gb.print #=> testb______________
|
21
|
+
gb.insert_at(6, 'u')
|
22
|
+
gb.print #=> testbu_____________
|
23
|
+
gb.previous #=> b
|
24
|
+
gb.next #=> u
|
25
|
+
gb.offset = 7
|
26
|
+
gb.put('f')
|
27
|
+
gb.print #=> testbuf____________
|
28
|
+
|
29
|
+
To run the test suite:
|
30
|
+
|
31
|
+
rake
|
32
|
+
|
33
|
+
Todo:
|
34
|
+
|
35
|
+
Better 1.9 compat
|
36
|
+
Look into moving off the C++ implementation
|
37
|
+
|
38
|
+
Work in progress, thanks for watching!
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
$:.unshift(File.expand_path('lib'))
|
5
|
+
GAP_BUFFER_ROOT = 'ext/gap_buffer'
|
6
|
+
|
7
|
+
desc 'Default: test'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Run gap_buffer tests.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs = [GAP_BUFFER_ROOT]
|
13
|
+
t.pattern = 'test/test_*.rb'
|
14
|
+
t.ruby_opts << '-rtest'
|
15
|
+
t.libs << 'test'
|
16
|
+
t.warning = true
|
17
|
+
t.verbose = true
|
18
|
+
end
|
19
|
+
task :test => :build
|
20
|
+
|
21
|
+
namespace :build do
|
22
|
+
file "#{GAP_BUFFER_ROOT}/rubymain.cpp"
|
23
|
+
file "#{GAP_BUFFER_ROOT}/extconf.rb"
|
24
|
+
file "#{GAP_BUFFER_ROOT}/Makefile" => %W(#{GAP_BUFFER_ROOT}/rubymain.cpp #{GAP_BUFFER_ROOT}/extconf.rb) do
|
25
|
+
Dir.chdir(GAP_BUFFER_ROOT) do
|
26
|
+
ruby 'extconf.rb'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "generate makefile"
|
31
|
+
task :makefile => %W(#{GAP_BUFFER_ROOT}/Makefile #{GAP_BUFFER_ROOT}/rubymain.cpp)
|
32
|
+
|
33
|
+
dlext = Config::CONFIG['DLEXT']
|
34
|
+
file "#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}" => %W(#{GAP_BUFFER_ROOT}/Makefile #{GAP_BUFFER_ROOT}/rubymain.cpp) do
|
35
|
+
Dir.chdir(GAP_BUFFER_ROOT) do
|
36
|
+
sh 'make' # TODO - is there a config for which make somewhere?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "compile gap buffer extension"
|
41
|
+
task :compile => "#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}"
|
42
|
+
|
43
|
+
task :clean do
|
44
|
+
Dir.chdir(GAP_BUFFER_ROOT) do
|
45
|
+
sh 'make clean'
|
46
|
+
end if File.exists?("#{GAP_BUFFER_ROOT}/Makefile")
|
47
|
+
end
|
48
|
+
|
49
|
+
CLEAN.include("#{GAP_BUFFER_ROOT}/Makefile")
|
50
|
+
CLEAN.include("#{GAP_BUFFER_ROOT}/gap_buffer.#{dlext}")
|
51
|
+
end
|
52
|
+
|
53
|
+
task :clean => %w(build:clean)
|
54
|
+
|
55
|
+
desc "compile"
|
56
|
+
task :build => %w(build:compile)
|
57
|
+
|
58
|
+
task :install do |t|
|
59
|
+
Dir.chdir(GAP_BUFFER_ROOT) do
|
60
|
+
sh 'sudo make install'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "clean build install"
|
65
|
+
task :setup => %w(clean build install)
|
66
|
+
|
67
|
+
desc 'Run benchmarks'
|
68
|
+
task :bench do
|
69
|
+
ruby "bench/gap_buffer.rb"
|
70
|
+
end
|
71
|
+
task :bench => :build
|
data/bench/gap_buffer.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$:.unshift "."
|
2
|
+
require 'benchmark'
|
3
|
+
require File.dirname(__FILE__) + '/../ext/gap_buffer/gap_buffer'
|
4
|
+
|
5
|
+
TESTS = 1000
|
6
|
+
ITEMS = (0..TESTS).to_a
|
7
|
+
|
8
|
+
def prepare
|
9
|
+
string = ITEMS.join
|
10
|
+
gb = GapBuffer.new(TESTS)
|
11
|
+
gb << ITEMS.join
|
12
|
+
[string, gb]
|
13
|
+
end
|
14
|
+
|
15
|
+
Benchmark.bmbm do |results|
|
16
|
+
results.report("[String] random insert") do
|
17
|
+
string, gb = prepare
|
18
|
+
TESTS.times{ r = rand(TESTS); string[r] = r.to_s }
|
19
|
+
end
|
20
|
+
results.report("[GapBuffer] random insert") do
|
21
|
+
string, gb = prepare
|
22
|
+
TESTS.times{ r = rand(TESTS); gb.insert_at(r, r.to_s); }
|
23
|
+
end
|
24
|
+
results.report("[String] insert") do
|
25
|
+
string, gb = prepare
|
26
|
+
TESTS.times{|i| string[i] = i.to_s }
|
27
|
+
end
|
28
|
+
results.report("[GapBuffer] insert") do
|
29
|
+
string, gb = prepare
|
30
|
+
TESTS.times{|i| gb.insert_at(i, i.to_s); }
|
31
|
+
end
|
32
|
+
results.report("[String] random remove") do
|
33
|
+
string, gb = prepare
|
34
|
+
TESTS.times{ r = rand(TESTS); string.delete!(r.to_s) }
|
35
|
+
end
|
36
|
+
results.report("[GapBuffer] random remove") do
|
37
|
+
string, gb = prepare
|
38
|
+
TESTS.times{ r = rand(TESTS); gb.delete(r) }
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
dir_config('gap_buffer')
|
4
|
+
have_func('rb_thread_blocking_region')
|
5
|
+
|
6
|
+
# Shamelessly leached from Eventmachine (https://github.com/eventmachine/eventmachine)
|
7
|
+
|
8
|
+
case RUBY_PLATFORM
|
9
|
+
when /mswin32/, /mingw32/, /bccwin32/
|
10
|
+
check_heads(%w[windows.h winsock.h], true)
|
11
|
+
check_libs(%w[kernel32 rpcrt4 gdi32], true)
|
12
|
+
|
13
|
+
if GNU_CHAIN
|
14
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++"
|
15
|
+
else
|
16
|
+
$defs.push "-EHs"
|
17
|
+
$defs.push "-GR"
|
18
|
+
end
|
19
|
+
|
20
|
+
when /solaris/
|
21
|
+
add_define 'OS_SOLARIS8'
|
22
|
+
check_libs(%w[nsl socket], true)
|
23
|
+
|
24
|
+
if CONFIG['CC'] == 'cc' and `cc -flags 2>&1` =~ /Sun/ # detect SUNWspro compiler
|
25
|
+
# SUN CHAIN
|
26
|
+
add_define 'CC_SUNWspro'
|
27
|
+
$preload = ["\nCXX = CC"] # hack a CXX= line into the makefile
|
28
|
+
$CFLAGS = CONFIG['CFLAGS'] = "-KPIC"
|
29
|
+
CONFIG['CCDLFLAGS'] = "-KPIC"
|
30
|
+
CONFIG['LDSHARED'] = "$(CXX) -G -KPIC -lCstd"
|
31
|
+
else
|
32
|
+
# GNU CHAIN
|
33
|
+
# on Unix we need a g++ link, not gcc.
|
34
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared"
|
35
|
+
end
|
36
|
+
|
37
|
+
when /openbsd/
|
38
|
+
# OpenBSD branch contributed by Guillaume Sellier.
|
39
|
+
|
40
|
+
# on Unix we need a g++ link, not gcc. On OpenBSD, linking against libstdc++ have to be explicitly done for shared libs
|
41
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared -lstdc++ -fPIC"
|
42
|
+
CONFIG['LDSHAREDXX'] = "$(CXX) -shared -lstdc++ -fPIC"
|
43
|
+
|
44
|
+
when /darwin/
|
45
|
+
# on Unix we need a g++ link, not gcc.
|
46
|
+
# Ff line contributed by Daniel Harple.
|
47
|
+
CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ')
|
48
|
+
|
49
|
+
when /linux/
|
50
|
+
# on Unix we need a g++ link, not gcc.
|
51
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared"
|
52
|
+
|
53
|
+
when /aix/
|
54
|
+
# on Unix we need a g++ link, not gcc.
|
55
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared -Wl,-G"
|
56
|
+
|
57
|
+
else
|
58
|
+
# on Unix we need a g++ link, not gcc.
|
59
|
+
CONFIG['LDSHARED'] = "$(CXX) -shared"
|
60
|
+
end
|
61
|
+
|
62
|
+
create_makefile('gap_buffer')
|
@@ -0,0 +1,480 @@
|
|
1
|
+
/*
|
2
|
+
* gap_buffer.cpp
|
3
|
+
*
|
4
|
+
* Author: Hsin Tsao (stsao@lazyhacker.com)
|
5
|
+
* Version: 1.0 (June 12, 2003)
|
6
|
+
*
|
7
|
+
* This provides the implementation to the GapBuffer class defined
|
8
|
+
* in text_buffer.h.
|
9
|
+
*
|
10
|
+
* Portions of this work derived from Joseph Allen's usenet
|
11
|
+
* postings on comp.editor that was released to the public
|
12
|
+
* domain.
|
13
|
+
*
|
14
|
+
*
|
15
|
+
* There are no restrictions on the use of this code other
|
16
|
+
* than to include my name in any derived work. There are
|
17
|
+
* no warranty for this obviously, but you are welcomed
|
18
|
+
* to modify, correct, or adapt the code to your needs. The
|
19
|
+
* author appreciates if you are willing to submit corrections
|
20
|
+
* or suggestions for improvement.
|
21
|
+
*
|
22
|
+
*
|
23
|
+
* http://www.lazyhacker.com
|
24
|
+
*/
|
25
|
+
|
26
|
+
#include <iostream>
|
27
|
+
#include <cstdlib>
|
28
|
+
#include <stdio.h>
|
29
|
+
#include <string.h>
|
30
|
+
#include <sys/stat.h>
|
31
|
+
#include "gap_buffer.h"
|
32
|
+
|
33
|
+
using namespace std;
|
34
|
+
|
35
|
+
GapBuffer::GapBuffer(int gsize) : GAP_SIZE(gsize)
|
36
|
+
{
|
37
|
+
InitBuffer(GAP_SIZE);
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
GapBuffer::GapBuffer(FILE *file, int gsize) : GAP_SIZE(gsize)
|
43
|
+
{
|
44
|
+
|
45
|
+
// determine the size of the file then create
|
46
|
+
// a buffer of size + GAP_SIZE
|
47
|
+
struct stat buf;
|
48
|
+
|
49
|
+
fstat(fileno(file), &buf);
|
50
|
+
long file_size = buf.st_size;
|
51
|
+
InitBuffer(file_size + GAP_SIZE);
|
52
|
+
MoveGapToPoint();
|
53
|
+
ExpandGap( (int)file_size );
|
54
|
+
unsigned int amount = fread(gapstart, 1, file_size, file);
|
55
|
+
|
56
|
+
gapstart += amount;
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
}
|
61
|
+
|
62
|
+
/* Copy Constructor - Since we have pointers
|
63
|
+
* as member data, we need to provide our own
|
64
|
+
* copy constructor because the default will
|
65
|
+
* only cause copies to all point to the same
|
66
|
+
* location.
|
67
|
+
*/
|
68
|
+
GapBuffer::GapBuffer(const GapBuffer& tb)
|
69
|
+
{
|
70
|
+
GAP_SIZE = tb.GAP_SIZE;
|
71
|
+
|
72
|
+
buffer = (char *) malloc(tb.bufend - tb.buffer);
|
73
|
+
|
74
|
+
strcpy(buffer, tb.buffer);
|
75
|
+
|
76
|
+
bufend = buffer + (tb.bufend - tb.buffer);
|
77
|
+
gapstart = buffer + (tb.gapstart - tb.buffer);
|
78
|
+
gapend = gapstart + (tb.gapend - tb.gapstart);
|
79
|
+
point = buffer + (tb.point - tb.buffer);
|
80
|
+
|
81
|
+
}
|
82
|
+
|
83
|
+
|
84
|
+
GapBuffer::~GapBuffer()
|
85
|
+
{
|
86
|
+
|
87
|
+
if (buffer) {
|
88
|
+
free(buffer);
|
89
|
+
}
|
90
|
+
|
91
|
+
}
|
92
|
+
|
93
|
+
/*
|
94
|
+
* Copy the characters from one location to another. We have
|
95
|
+
* to write our own instead of using memcopy because we are
|
96
|
+
* working within a single linear buffer and thus can have
|
97
|
+
* overlap between the source and destination.
|
98
|
+
*/
|
99
|
+
int GapBuffer::CopyBytes(char *destination, char *source, unsigned int length)
|
100
|
+
{
|
101
|
+
|
102
|
+
if ( (destination == source) || (length == 0) ) {
|
103
|
+
return 1;
|
104
|
+
}
|
105
|
+
|
106
|
+
// if we're moving the character toward the front of the buffer
|
107
|
+
if (source > destination) {
|
108
|
+
|
109
|
+
// check to make sure that we don't go beyond the buffer
|
110
|
+
if ( (source + length) >= bufend ) {
|
111
|
+
return 0;
|
112
|
+
}
|
113
|
+
|
114
|
+
for (; length > 0; length--) {
|
115
|
+
*(destination++) = *(source++);
|
116
|
+
}
|
117
|
+
|
118
|
+
} else {
|
119
|
+
|
120
|
+
// To prevent overwriting characters we still
|
121
|
+
// need to move, go to the back and copy forward.
|
122
|
+
source += length;
|
123
|
+
destination += length;
|
124
|
+
|
125
|
+
for (; length > 0; length--) {
|
126
|
+
// decrement first 'cause we start one byte beyond where we want
|
127
|
+
*(--destination) = *(--source);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
return 1;
|
132
|
+
|
133
|
+
}
|
134
|
+
|
135
|
+
/*
|
136
|
+
* Expand the buffer to new size + GAP_SIZE.
|
137
|
+
*
|
138
|
+
*/
|
139
|
+
void GapBuffer::ExpandBuffer(unsigned int size)
|
140
|
+
{
|
141
|
+
|
142
|
+
// Check to see that we actually need to increase the buffer
|
143
|
+
// since BufferSize doesn't include the gap.
|
144
|
+
if ( ( (bufend - buffer) + size) > BufferSize() ) {
|
145
|
+
|
146
|
+
char *origbuffer = buffer;
|
147
|
+
|
148
|
+
int NewBufferSize = (bufend - buffer) + size + GAP_SIZE;
|
149
|
+
|
150
|
+
buffer = (char *) realloc(buffer, NewBufferSize);
|
151
|
+
|
152
|
+
point += buffer - origbuffer;
|
153
|
+
bufend += buffer - origbuffer;
|
154
|
+
gapstart += buffer - origbuffer;
|
155
|
+
gapend += buffer - origbuffer;
|
156
|
+
}
|
157
|
+
|
158
|
+
}
|
159
|
+
|
160
|
+
/*
|
161
|
+
* Move the gap to the current position of the point.
|
162
|
+
* The point should end in same location as gapstart.
|
163
|
+
*/
|
164
|
+
void GapBuffer::MoveGapToPoint()
|
165
|
+
{
|
166
|
+
|
167
|
+
|
168
|
+
if (point == gapstart) {
|
169
|
+
return;
|
170
|
+
}
|
171
|
+
|
172
|
+
if (point == gapend) {
|
173
|
+
point = gapstart;
|
174
|
+
return;
|
175
|
+
}
|
176
|
+
|
177
|
+
// Move gap towards the left
|
178
|
+
if (point < gapstart) {
|
179
|
+
// Move the point over by gapsize.
|
180
|
+
CopyBytes(point + (gapend-gapstart), point, gapstart - point);
|
181
|
+
gapend -= (gapstart - point);
|
182
|
+
gapstart = point;
|
183
|
+
} else {
|
184
|
+
// Since point is after the gap, find distance
|
185
|
+
// between gapend and point and that's how
|
186
|
+
// much we move from gapend to gapstart.
|
187
|
+
CopyBytes(gapstart, gapend, point - gapend);
|
188
|
+
gapstart += point - gapend;
|
189
|
+
gapend = point;
|
190
|
+
point = gapstart;
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
/*
|
195
|
+
* Expand the size of the gap. If the required
|
196
|
+
* size is less then the current gap size, do
|
197
|
+
* nothing. If the size is greater than the
|
198
|
+
* current size, increase the gap to the default
|
199
|
+
* gap size + size.
|
200
|
+
*/
|
201
|
+
void GapBuffer::ExpandGap(unsigned int size)
|
202
|
+
{
|
203
|
+
|
204
|
+
|
205
|
+
if (size > SizeOfGap()) {
|
206
|
+
size += GAP_SIZE;
|
207
|
+
ExpandBuffer(size);
|
208
|
+
CopyBytes(gapend+size, gapend, bufend - gapend);
|
209
|
+
|
210
|
+
gapend += size;
|
211
|
+
bufend += size;
|
212
|
+
}
|
213
|
+
|
214
|
+
}
|
215
|
+
|
216
|
+
/*
|
217
|
+
* Set point to offset from start of buffer.
|
218
|
+
*/
|
219
|
+
void GapBuffer::SetPoint(unsigned int offset)
|
220
|
+
{
|
221
|
+
|
222
|
+
point = buffer + offset;
|
223
|
+
|
224
|
+
if (point > gapstart) {
|
225
|
+
point += gapend - gapstart;
|
226
|
+
}
|
227
|
+
|
228
|
+
}
|
229
|
+
|
230
|
+
/*
|
231
|
+
* Returns the current size of the gap.
|
232
|
+
*/
|
233
|
+
int GapBuffer::SizeOfGap()
|
234
|
+
{
|
235
|
+
return gapend - gapstart;
|
236
|
+
|
237
|
+
}
|
238
|
+
|
239
|
+
/*
|
240
|
+
* Returns offset from point to start of buffer.
|
241
|
+
*/
|
242
|
+
unsigned int GapBuffer::PointOffset()
|
243
|
+
{
|
244
|
+
|
245
|
+
if (point > gapend) {
|
246
|
+
return ((point - buffer) - (gapend - gapstart));
|
247
|
+
} else {
|
248
|
+
return (point - buffer);
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
/*
|
253
|
+
* Return character that point is pointing to.
|
254
|
+
* If point is inside the gap, then return the
|
255
|
+
* the first character outside the gap.
|
256
|
+
*/
|
257
|
+
char GapBuffer::GetChar()
|
258
|
+
{
|
259
|
+
|
260
|
+
// If the point is anywhere in the gap, then
|
261
|
+
// it should always be at the start of the gap.
|
262
|
+
if (point == gapstart) {
|
263
|
+
point = gapend;
|
264
|
+
}
|
265
|
+
|
266
|
+
return *point;
|
267
|
+
}
|
268
|
+
|
269
|
+
/*
|
270
|
+
* Return the previous character and
|
271
|
+
* move point back one position.
|
272
|
+
*/
|
273
|
+
char GapBuffer::PreviousChar()
|
274
|
+
{
|
275
|
+
|
276
|
+
if (point == gapend) {
|
277
|
+
point = gapstart;
|
278
|
+
}
|
279
|
+
|
280
|
+
return *(--point);
|
281
|
+
}
|
282
|
+
|
283
|
+
/*
|
284
|
+
* Replace the character of point.
|
285
|
+
*/
|
286
|
+
void GapBuffer::ReplaceChar(char ch)
|
287
|
+
{
|
288
|
+
|
289
|
+
// Since we're just replacing the current character,
|
290
|
+
// we don't need to move or modify the gap.
|
291
|
+
if (point == gapstart) {
|
292
|
+
point = gapend;
|
293
|
+
}
|
294
|
+
|
295
|
+
if (point == bufend) {
|
296
|
+
ExpandBuffer(1);
|
297
|
+
bufend++;
|
298
|
+
}
|
299
|
+
|
300
|
+
*point = ch;
|
301
|
+
}
|
302
|
+
|
303
|
+
/*
|
304
|
+
* Increment pointer. Returns next character.
|
305
|
+
*/
|
306
|
+
char GapBuffer::NextChar()
|
307
|
+
{
|
308
|
+
// point should not be in the gap.
|
309
|
+
if (point == gapstart) {
|
310
|
+
point = gapend;
|
311
|
+
return *point;
|
312
|
+
}
|
313
|
+
|
314
|
+
return *(++point);
|
315
|
+
|
316
|
+
}
|
317
|
+
|
318
|
+
void GapBuffer::PutChar(char ch)
|
319
|
+
{
|
320
|
+
InsertChar(ch);
|
321
|
+
*point++;
|
322
|
+
|
323
|
+
}
|
324
|
+
|
325
|
+
/*
|
326
|
+
* Insert character at point position. Note
|
327
|
+
* that repeatedly calling this function will
|
328
|
+
* keep calling MoveGapToPoint since this function
|
329
|
+
* doesn't advance the point. The result is the
|
330
|
+
* text appears reverse of key strokes.
|
331
|
+
*/
|
332
|
+
void GapBuffer::InsertChar(char ch)
|
333
|
+
{
|
334
|
+
// Here we do need to move the gap if the point
|
335
|
+
// is not already at the start of the gap.
|
336
|
+
|
337
|
+
if (point != gapstart) {
|
338
|
+
MoveGapToPoint();
|
339
|
+
}
|
340
|
+
|
341
|
+
// check to make sure that the gap has room
|
342
|
+
if (gapstart == gapend) {
|
343
|
+
ExpandGap(1);
|
344
|
+
}
|
345
|
+
|
346
|
+
*(gapstart++) = ch;
|
347
|
+
|
348
|
+
}
|
349
|
+
|
350
|
+
/*
|
351
|
+
* Delete "size" number of characters.
|
352
|
+
*/
|
353
|
+
void GapBuffer::DeleteChars(unsigned int size)
|
354
|
+
{
|
355
|
+
|
356
|
+
if (point != gapstart) {
|
357
|
+
MoveGapToPoint();
|
358
|
+
}
|
359
|
+
|
360
|
+
// We shifted the gap so that gapend points to the location
|
361
|
+
// where we want to start deleting so extend it
|
362
|
+
// to cover all the characters.
|
363
|
+
gapend += size;
|
364
|
+
}
|
365
|
+
|
366
|
+
void GapBuffer::InsertString(char *string, unsigned int length)
|
367
|
+
{
|
368
|
+
|
369
|
+
MoveGapToPoint();
|
370
|
+
|
371
|
+
if (length > SizeOfGap()) {
|
372
|
+
ExpandGap(length);
|
373
|
+
}
|
374
|
+
|
375
|
+
do {
|
376
|
+
PutChar(*(string++));
|
377
|
+
} while ( length-- );
|
378
|
+
}
|
379
|
+
|
380
|
+
/*
|
381
|
+
* Here we initilize the buffer and set
|
382
|
+
* the pointers to the correct position.
|
383
|
+
*/
|
384
|
+
int GapBuffer::InitBuffer(unsigned int size)
|
385
|
+
{
|
386
|
+
|
387
|
+
/*
|
388
|
+
if (buffer) {
|
389
|
+
free(buffer);
|
390
|
+
}
|
391
|
+
*/
|
392
|
+
buffer = NULL;
|
393
|
+
buffer = (char *) malloc(size);
|
394
|
+
|
395
|
+
if (!buffer) {
|
396
|
+
return 0;
|
397
|
+
}
|
398
|
+
|
399
|
+
point = buffer;
|
400
|
+
gapstart = buffer;
|
401
|
+
|
402
|
+
// initially gapend is outside of buffer
|
403
|
+
gapend = buffer + size;
|
404
|
+
bufend = gapend;
|
405
|
+
|
406
|
+
return 1;
|
407
|
+
|
408
|
+
}
|
409
|
+
|
410
|
+
int GapBuffer::BufferSize()
|
411
|
+
{
|
412
|
+
return (bufend - buffer) - (gapend - gapstart);
|
413
|
+
}
|
414
|
+
|
415
|
+
char* GapBuffer::GetBuffer()
|
416
|
+
{
|
417
|
+
return buffer;
|
418
|
+
}
|
419
|
+
|
420
|
+
void GapBuffer::PrintBuffer()
|
421
|
+
{
|
422
|
+
/*
|
423
|
+
char ch;
|
424
|
+
|
425
|
+
cout << "Printing the buffer: " << endl;
|
426
|
+
SetPoint(0);
|
427
|
+
while (point < bufend) {
|
428
|
+
cout << GetCharMovePoint();
|
429
|
+
}
|
430
|
+
|
431
|
+
cout << "Printing the buffer in reverse: " << endl;
|
432
|
+
|
433
|
+
while (point >= buffer) {
|
434
|
+
cout << GetPrevCharMovePoint();
|
435
|
+
}
|
436
|
+
*/
|
437
|
+
|
438
|
+
char *temp = buffer;
|
439
|
+
|
440
|
+
|
441
|
+
while (temp < bufend) {
|
442
|
+
|
443
|
+
if ( (temp >= gapstart) && (temp < gapend) ) {
|
444
|
+
cout << "_";
|
445
|
+
temp++;
|
446
|
+
} else {
|
447
|
+
cout << *(temp++);
|
448
|
+
}
|
449
|
+
|
450
|
+
}
|
451
|
+
cout << endl;
|
452
|
+
}
|
453
|
+
|
454
|
+
int GapBuffer::SaveBufferToFile(FILE *file, unsigned int bytes)
|
455
|
+
{
|
456
|
+
|
457
|
+
if (!bytes) {
|
458
|
+
return 1;
|
459
|
+
}
|
460
|
+
|
461
|
+
if (point == gapstart) {
|
462
|
+
point = gapend;
|
463
|
+
}
|
464
|
+
|
465
|
+
if ( (gapstart > point) && (gapstart < (point + bytes)) && (gapstart != gapend) ) {
|
466
|
+
if ( gapstart - point != fwrite(point, 1, gapstart-point, file) ) {
|
467
|
+
return 0;
|
468
|
+
}
|
469
|
+
|
470
|
+
if ( (bytes - (gapstart - point)) != fwrite(gapend, 1, bytes-(gapstart - point), file) ) {
|
471
|
+
return 1;
|
472
|
+
}
|
473
|
+
|
474
|
+
return 1;
|
475
|
+
} else {
|
476
|
+
return bytes == fwrite(point, 1, bytes, file);
|
477
|
+
}
|
478
|
+
|
479
|
+
|
480
|
+
}
|
@@ -0,0 +1,159 @@
|
|
1
|
+
/*
|
2
|
+
* gap_buffer.h
|
3
|
+
*
|
4
|
+
* Author: Hsin Tsao (stsao@lazyhacker.com)
|
5
|
+
* Version: 1.0 (June 12, 2003)
|
6
|
+
*
|
7
|
+
* A text buffer class using the buffer-gap technique for managing
|
8
|
+
* the text stored in the buffer.
|
9
|
+
*
|
10
|
+
* Portions of this work derived from Joseph Allen's usenet
|
11
|
+
* postings on comp.editor that was released to the public
|
12
|
+
* domain.
|
13
|
+
*
|
14
|
+
*
|
15
|
+
* There are no restrictions on the use of this code other
|
16
|
+
* than to include my name in any derived work. There are
|
17
|
+
* no warranty for this obviously, but you are welcomed
|
18
|
+
* to modify, correct, or adapt the code to your needs. The
|
19
|
+
* author appreciates if you are willing to submit corrections
|
20
|
+
* or suggestions for improvement.
|
21
|
+
*
|
22
|
+
*
|
23
|
+
* http://www.lazyhacker.com
|
24
|
+
*/
|
25
|
+
|
26
|
+
#include <stdio.h>
|
27
|
+
|
28
|
+
class GapBuffer {
|
29
|
+
|
30
|
+
char *point; // location pointer into buffer
|
31
|
+
char *buffer; // start of text buffer
|
32
|
+
char *bufend; // first location outside buffer
|
33
|
+
char *gapstart; // start of gap
|
34
|
+
char *gapend; // first location after end of gap
|
35
|
+
|
36
|
+
unsigned int GAP_SIZE; // expand GAP by this value
|
37
|
+
|
38
|
+
int InitBuffer(unsigned int size);
|
39
|
+
|
40
|
+
/*
|
41
|
+
* Copy the characters from one location to another. We have
|
42
|
+
* to write our own instead of using memcopy because we are
|
43
|
+
* working within a single linear buffer and thus can have
|
44
|
+
* overlap between the source and destination.
|
45
|
+
*/
|
46
|
+
int CopyBytes(char *destination, char *source, unsigned int length);
|
47
|
+
|
48
|
+
/*
|
49
|
+
* Expand the size of the buffer.
|
50
|
+
*/
|
51
|
+
void ExpandBuffer(unsigned int size);
|
52
|
+
|
53
|
+
/*
|
54
|
+
* Expand the size of the gap.
|
55
|
+
*/
|
56
|
+
void ExpandGap(unsigned int size);
|
57
|
+
|
58
|
+
public:
|
59
|
+
|
60
|
+
static const int DEFAULT_GAP_SIZE=20;
|
61
|
+
|
62
|
+
/* Constructor with default gap size. */
|
63
|
+
GapBuffer(int gsize=DEFAULT_GAP_SIZE);
|
64
|
+
|
65
|
+
/* Constructor with instantiating with an existing file. */
|
66
|
+
GapBuffer(FILE *file, int gsize=DEFAULT_GAP_SIZE);
|
67
|
+
|
68
|
+
/* Copy constructor to deal with our pointer members. */
|
69
|
+
GapBuffer(const GapBuffer& tb);
|
70
|
+
|
71
|
+
~GapBuffer();
|
72
|
+
|
73
|
+
/*
|
74
|
+
* Returns the size of the buffer minus the gap.
|
75
|
+
*/
|
76
|
+
int BufferSize();
|
77
|
+
|
78
|
+
/*
|
79
|
+
* Move the gap to the current position of the point.
|
80
|
+
*/
|
81
|
+
void MoveGapToPoint();
|
82
|
+
|
83
|
+
/*
|
84
|
+
* Set point to offset from start of buffer.
|
85
|
+
*/
|
86
|
+
void SetPoint(unsigned int offset);
|
87
|
+
|
88
|
+
/*
|
89
|
+
* Returns the current size of the gap.
|
90
|
+
*/
|
91
|
+
int SizeOfGap();
|
92
|
+
|
93
|
+
/*
|
94
|
+
* Returns offset from point to start of buffer.
|
95
|
+
*/
|
96
|
+
unsigned int PointOffset();
|
97
|
+
|
98
|
+
/*
|
99
|
+
* Return character that point is pointing to.
|
100
|
+
* If point is inside the gap, then return the
|
101
|
+
* the first character outside the gap.
|
102
|
+
*/
|
103
|
+
char GetChar();
|
104
|
+
|
105
|
+
/*
|
106
|
+
* Return the previous character and
|
107
|
+
* move point back one position.
|
108
|
+
*/
|
109
|
+
char PreviousChar();
|
110
|
+
|
111
|
+
/*
|
112
|
+
* Replace the character of point. Does
|
113
|
+
* not move the gap.
|
114
|
+
*/
|
115
|
+
void ReplaceChar(char ch);
|
116
|
+
|
117
|
+
/*
|
118
|
+
* Get the next character and increment point.
|
119
|
+
*/
|
120
|
+
char NextChar();
|
121
|
+
|
122
|
+
/*
|
123
|
+
* Inserts a character at point location
|
124
|
+
* and advance the point.
|
125
|
+
*/
|
126
|
+
void PutChar(char ch);
|
127
|
+
|
128
|
+
/*
|
129
|
+
* Insert character at point position, but
|
130
|
+
* does NOT advance the point.
|
131
|
+
*/
|
132
|
+
void InsertChar(char ch);
|
133
|
+
|
134
|
+
/*
|
135
|
+
* Delete "size" number of characters.
|
136
|
+
*/
|
137
|
+
void DeleteChars(unsigned int size);
|
138
|
+
|
139
|
+
/*
|
140
|
+
* Inserts a length size string
|
141
|
+
* at point.
|
142
|
+
*/
|
143
|
+
void InsertString(char *string, unsigned int length);
|
144
|
+
|
145
|
+
/*
|
146
|
+
* Prints out the current buffer from start
|
147
|
+
* to end.
|
148
|
+
*/
|
149
|
+
void PrintBuffer();
|
150
|
+
|
151
|
+
/*
|
152
|
+
* Saves to file the number of bytes starting from
|
153
|
+
* the point.
|
154
|
+
*/
|
155
|
+
int SaveBufferToFile(FILE *file, unsigned int bytes);
|
156
|
+
|
157
|
+
char *GetBuffer();
|
158
|
+
|
159
|
+
};
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#include "gap_buffer.h"
|
2
|
+
#include <ruby.h>
|
3
|
+
|
4
|
+
#ifndef RSTRING_PTR
|
5
|
+
#define RSTRING_PTR(obj) RSTRING(obj)->ptr
|
6
|
+
#endif
|
7
|
+
|
8
|
+
#ifndef RSTRING_LEN
|
9
|
+
#define RSTRING_LEN(obj) RSTRING(obj)->len
|
10
|
+
#endif
|
11
|
+
|
12
|
+
#define GetGapBuffer(obj) \
|
13
|
+
GapBuffer *gb = NULL; \
|
14
|
+
Data_Get_Struct((obj), GapBuffer, gb); \
|
15
|
+
if (!gb) \
|
16
|
+
rb_raise(rb_eException, "No gap buffer!");
|
17
|
+
|
18
|
+
#define GapBufferChar(chr) \
|
19
|
+
char c = (chr); \
|
20
|
+
if (c == 0) return rb_str_tmp_new(0); \
|
21
|
+
return rb_str_new(&c, 1);
|
22
|
+
|
23
|
+
VALUE rb_cGapBuffer;
|
24
|
+
|
25
|
+
static void rb_gap_buffer_free(void *gb)
|
26
|
+
{
|
27
|
+
if(gb) delete((GapBuffer*) gb);
|
28
|
+
}
|
29
|
+
|
30
|
+
static VALUE rb_gap_buffer_new(int argc, VALUE *argv, VALUE obj)
|
31
|
+
{
|
32
|
+
VALUE size;
|
33
|
+
rb_scan_args(argc, argv, "01", &size);
|
34
|
+
if (NIL_P(size)) size = rb_const_get(rb_cGapBuffer, rb_intern("GAP_SIZE"));
|
35
|
+
GapBuffer *gb = NULL;
|
36
|
+
gb = new GapBuffer(NUM2INT(size));
|
37
|
+
if (!gb)
|
38
|
+
rb_raise(rb_eException, "No gap buffer!");
|
39
|
+
return Data_Wrap_Struct(rb_cGapBuffer, 0, rb_gap_buffer_free, (void*)gb);
|
40
|
+
}
|
41
|
+
|
42
|
+
static VALUE rb_gap_buffer_size(VALUE obj)
|
43
|
+
{
|
44
|
+
GetGapBuffer(obj);
|
45
|
+
return INT2FIX(gb->BufferSize());
|
46
|
+
}
|
47
|
+
|
48
|
+
static VALUE rb_gap_buffer_gap_size(VALUE obj)
|
49
|
+
{
|
50
|
+
GetGapBuffer(obj);
|
51
|
+
return INT2FIX(gb->SizeOfGap());
|
52
|
+
}
|
53
|
+
|
54
|
+
static VALUE rb_gap_buffer_print(VALUE obj)
|
55
|
+
{
|
56
|
+
GetGapBuffer(obj);
|
57
|
+
gb->PrintBuffer();
|
58
|
+
return Qnil;
|
59
|
+
}
|
60
|
+
|
61
|
+
static VALUE rb_gap_buffer_append(VALUE obj, VALUE str)
|
62
|
+
{
|
63
|
+
GetGapBuffer(obj);
|
64
|
+
Check_Type(str, T_STRING);
|
65
|
+
gb->InsertString(RSTRING_PTR(str), (unsigned int)RSTRING_LEN(str));
|
66
|
+
return obj;
|
67
|
+
}
|
68
|
+
|
69
|
+
static VALUE rb_gap_buffer_offset(VALUE obj)
|
70
|
+
{
|
71
|
+
GetGapBuffer(obj);
|
72
|
+
return INT2FIX(gb->PointOffset());
|
73
|
+
}
|
74
|
+
|
75
|
+
static VALUE rb_gap_buffer_offset_equals(VALUE obj, VALUE offset)
|
76
|
+
{
|
77
|
+
GetGapBuffer(obj);
|
78
|
+
gb->SetPoint(FIX2INT(offset));
|
79
|
+
return offset;
|
80
|
+
}
|
81
|
+
|
82
|
+
static VALUE rb_gap_buffer_move(VALUE obj)
|
83
|
+
{
|
84
|
+
GetGapBuffer(obj);
|
85
|
+
gb->MoveGapToPoint();
|
86
|
+
return rb_gap_buffer_offset(obj);
|
87
|
+
}
|
88
|
+
|
89
|
+
static VALUE rb_gap_buffer_get(VALUE obj)
|
90
|
+
{
|
91
|
+
GetGapBuffer(obj);
|
92
|
+
GapBufferChar(gb->GetChar());
|
93
|
+
}
|
94
|
+
|
95
|
+
static VALUE rb_gap_buffer_previous(VALUE obj)
|
96
|
+
{
|
97
|
+
GetGapBuffer(obj);
|
98
|
+
GapBufferChar(gb->PreviousChar());
|
99
|
+
}
|
100
|
+
|
101
|
+
static VALUE rb_gap_buffer_next(VALUE obj)
|
102
|
+
{
|
103
|
+
GetGapBuffer(obj);
|
104
|
+
GapBufferChar(gb->NextChar());
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE rb_gap_buffer_replace(VALUE obj, VALUE str)
|
108
|
+
{
|
109
|
+
GetGapBuffer(obj);
|
110
|
+
Check_Type(str, T_STRING);
|
111
|
+
char c = *RSTRING_PTR(str);
|
112
|
+
gb->ReplaceChar(c);
|
113
|
+
return Qnil;
|
114
|
+
}
|
115
|
+
|
116
|
+
static VALUE rb_gap_buffer_put(VALUE obj, VALUE str)
|
117
|
+
{
|
118
|
+
GetGapBuffer(obj);
|
119
|
+
Check_Type(str, T_STRING);
|
120
|
+
char c = *RSTRING_PTR(str);
|
121
|
+
gb->PutChar(c);
|
122
|
+
return Qnil;
|
123
|
+
}
|
124
|
+
|
125
|
+
static VALUE rb_gap_buffer_insert(VALUE obj, VALUE str)
|
126
|
+
{
|
127
|
+
GetGapBuffer(obj);
|
128
|
+
Check_Type(str, T_STRING);
|
129
|
+
char c = *RSTRING_PTR(str);
|
130
|
+
gb->InsertChar(c);
|
131
|
+
return Qnil;
|
132
|
+
}
|
133
|
+
|
134
|
+
static VALUE rb_gap_buffer_delete(VALUE obj, VALUE size)
|
135
|
+
{
|
136
|
+
GetGapBuffer(obj);
|
137
|
+
gb->DeleteChars(FIX2INT(size));
|
138
|
+
return Qnil;
|
139
|
+
}
|
140
|
+
|
141
|
+
static VALUE rb_gap_buffer_to_s(VALUE obj, VALUE size)
|
142
|
+
{
|
143
|
+
GetGapBuffer(obj);
|
144
|
+
return(rb_str_new2(gb->GetBuffer()));
|
145
|
+
}
|
146
|
+
|
147
|
+
static VALUE rb_gap_buffer_insert_at(VALUE obj, VALUE offset, VALUE str)
|
148
|
+
{
|
149
|
+
GetGapBuffer(obj);
|
150
|
+
Check_Type(str, T_STRING);
|
151
|
+
char c = *RSTRING_PTR(str);
|
152
|
+
gb->SetPoint(FIX2INT(offset));
|
153
|
+
gb->InsertChar(c);
|
154
|
+
return Qnil;
|
155
|
+
}
|
156
|
+
|
157
|
+
static VALUE rb_gap_buffer_delete_at(VALUE obj, VALUE offset)
|
158
|
+
{
|
159
|
+
GetGapBuffer(obj);
|
160
|
+
gb->SetPoint(FIX2INT(offset));
|
161
|
+
gb->DeleteChars(1);
|
162
|
+
return Qnil;
|
163
|
+
}
|
164
|
+
|
165
|
+
extern "C" void Init_gap_buffer()
|
166
|
+
{
|
167
|
+
rb_cGapBuffer = rb_define_class("GapBuffer", rb_cObject);
|
168
|
+
rb_define_const(rb_cGapBuffer, "GAP_SIZE", INT2NUM(20));
|
169
|
+
|
170
|
+
rb_define_module_function(rb_cGapBuffer, "new", (VALUE(*)(...))rb_gap_buffer_new, -1);
|
171
|
+
rb_define_method(rb_cGapBuffer, "size", (VALUE(*)(...))rb_gap_buffer_size, 0);
|
172
|
+
rb_define_method(rb_cGapBuffer, "gap_size", (VALUE(*)(...))rb_gap_buffer_gap_size, 0);
|
173
|
+
rb_define_method(rb_cGapBuffer, "print", (VALUE(*)(...))rb_gap_buffer_print, 0);
|
174
|
+
rb_define_method(rb_cGapBuffer, "<<", (VALUE(*)(...))rb_gap_buffer_append, 1);
|
175
|
+
rb_define_method(rb_cGapBuffer, "offset", (VALUE(*)(...))rb_gap_buffer_offset, 0);
|
176
|
+
rb_define_method(rb_cGapBuffer, "position", (VALUE(*)(...))rb_gap_buffer_offset, 0);
|
177
|
+
rb_define_method(rb_cGapBuffer, "offset=", (VALUE(*)(...))rb_gap_buffer_offset_equals, 1);
|
178
|
+
rb_define_method(rb_cGapBuffer, "position=", (VALUE(*)(...))rb_gap_buffer_offset_equals, 1);
|
179
|
+
rb_define_method(rb_cGapBuffer, "move", (VALUE(*)(...))rb_gap_buffer_move, 0);
|
180
|
+
rb_define_method(rb_cGapBuffer, "get", (VALUE(*)(...))rb_gap_buffer_get, 0);
|
181
|
+
rb_define_method(rb_cGapBuffer, "previous", (VALUE(*)(...))rb_gap_buffer_previous, 0);
|
182
|
+
rb_define_method(rb_cGapBuffer, "next", (VALUE(*)(...))rb_gap_buffer_next, 0);
|
183
|
+
rb_define_method(rb_cGapBuffer, "replace", (VALUE(*)(...))rb_gap_buffer_replace, 1);
|
184
|
+
rb_define_method(rb_cGapBuffer, "put", (VALUE(*)(...))rb_gap_buffer_put, 1);
|
185
|
+
rb_define_method(rb_cGapBuffer, "insert", (VALUE(*)(...))rb_gap_buffer_insert, 1);
|
186
|
+
rb_define_method(rb_cGapBuffer, "delete", (VALUE(*)(...))rb_gap_buffer_delete, 1);
|
187
|
+
rb_define_method(rb_cGapBuffer, "to_s", (VALUE(*)(...))rb_gap_buffer_to_s, 0);
|
188
|
+
rb_define_method(rb_cGapBuffer, "to_str", (VALUE(*)(...))rb_gap_buffer_to_s, 0);
|
189
|
+
rb_define_method(rb_cGapBuffer, "insert_at", (VALUE(*)(...))rb_gap_buffer_insert_at, 2);
|
190
|
+
rb_define_method(rb_cGapBuffer, "delete_at", (VALUE(*)(...))rb_gap_buffer_delete_at, 1);
|
191
|
+
}
|
data/gap_buffer.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'gap_buffer'
|
3
|
+
s.version = '0.1'
|
4
|
+
s.date = '2010-10-25'
|
5
|
+
s.authors = ['Lourens Naudé', 'Hsin Tsao']
|
6
|
+
s.email = ['lourens@methodmissing.com', 'stsao@lazyhacker.com']
|
7
|
+
s.description = 'WIP implementation of a Gap Buffer for Ruby MRI.'
|
8
|
+
s.homepage = 'http://github.com/methodmissing/gap_buffer'
|
9
|
+
s.summary = 'WIP implementation of a Gap Buffer for Ruby MRI.'
|
10
|
+
s.extensions = 'ext/gap_buffer/extconf.rb'
|
11
|
+
s.files = Dir.glob("{ext,test,bench}/**/*") + %w[README Rakefile gap_buffer.gemspec]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.extra_rdoc_files = Dir['ext/gap_buffer/*.c']
|
14
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
class TestGapBuffer < Test::Unit::TestCase
|
2
|
+
def test_initialize
|
3
|
+
assert_equal 20, GapBuffer::GAP_SIZE
|
4
|
+
assert_instance_of GapBuffer, GapBuffer.new(10)
|
5
|
+
assert_equal 10, GapBuffer.new(10).gap_size
|
6
|
+
assert_equal 20, GapBuffer.new.gap_size
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_size
|
10
|
+
gb = GapBuffer.new
|
11
|
+
assert_equal 0, gb.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_gap_size
|
15
|
+
gb = GapBuffer.new
|
16
|
+
assert_equal 20, gb.gap_size
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_print
|
20
|
+
gb = GapBuffer.new
|
21
|
+
gb.print
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_append
|
25
|
+
gb = GapBuffer.new
|
26
|
+
gb << 'test'
|
27
|
+
assert_equal 'test', gb.to_s
|
28
|
+
assert_equal 5, gb.offset
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_offset
|
32
|
+
gb = GapBuffer.new
|
33
|
+
assert_equal 0, gb.offset
|
34
|
+
gb << 'test'
|
35
|
+
assert_equal 5, gb.offset
|
36
|
+
gb << 'buffer'
|
37
|
+
assert_equal 12, gb.offset
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_offset_equals
|
41
|
+
gb = GapBuffer.new
|
42
|
+
assert_equal 0, gb.offset
|
43
|
+
gb.offset = 5
|
44
|
+
assert_equal 5, gb.offset
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_move
|
48
|
+
gb = GapBuffer.new
|
49
|
+
assert_equal 0, gb.offset
|
50
|
+
gb.offset = 5
|
51
|
+
gb.move
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_get
|
55
|
+
gb = GapBuffer.new
|
56
|
+
gb << 'test'
|
57
|
+
gb.offset = 2
|
58
|
+
assert_equal 's', gb.get
|
59
|
+
assert_equal 2, gb.offset
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_previous
|
63
|
+
gb = GapBuffer.new
|
64
|
+
gb << 'test'
|
65
|
+
assert_equal '', gb.previous
|
66
|
+
gb.offset = 2
|
67
|
+
assert_equal 'e', gb.previous
|
68
|
+
assert_equal 1, gb.offset
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_next
|
72
|
+
gb = GapBuffer.new
|
73
|
+
gb << 'test'
|
74
|
+
assert_equal '', gb.previous
|
75
|
+
gb.offset = 2
|
76
|
+
assert_equal 't', gb.next
|
77
|
+
assert_equal 3, gb.offset
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_delete
|
81
|
+
gb = GapBuffer.new
|
82
|
+
gb << 'test'
|
83
|
+
assert_equal '', gb.previous
|
84
|
+
gb.offset = 2
|
85
|
+
gb.delete(2)
|
86
|
+
assert_equal 'test', gb.to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_to_s
|
90
|
+
gb = GapBuffer.new
|
91
|
+
gb << 'test'
|
92
|
+
assert_equal 'test', gb.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_replace
|
96
|
+
gb = GapBuffer.new
|
97
|
+
gb << 'test'
|
98
|
+
gb.offset = 2
|
99
|
+
gb.replace('x')
|
100
|
+
assert_equal 'text', gb.to_s
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_insert
|
104
|
+
gb = GapBuffer.new
|
105
|
+
gb << 'test'
|
106
|
+
gb.offset = 2
|
107
|
+
gb.insert('x')
|
108
|
+
assert_equal 'text', gb.to_s
|
109
|
+
gb.insert('zz')
|
110
|
+
assert_equal 'tezt', gb.to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_insert_at
|
114
|
+
gb = GapBuffer.new
|
115
|
+
gb << 'test'
|
116
|
+
gb.insert_at(2, 'x')
|
117
|
+
assert_equal 'text', gb.to_s
|
118
|
+
gb.insert_at(3, 'z')
|
119
|
+
assert_equal 'texz', gb.to_s
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_delete_at
|
123
|
+
gb = GapBuffer.new
|
124
|
+
gb << 'test'
|
125
|
+
gb.insert_at(2, 'x')
|
126
|
+
assert_equal 'text', gb.to_s
|
127
|
+
gb.delete_at(2)
|
128
|
+
assert_equal 'text', gb.to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_put
|
132
|
+
gb = GapBuffer.new
|
133
|
+
gb << 'test'
|
134
|
+
gb.offset = 2
|
135
|
+
gb.put('x')
|
136
|
+
assert_equal 'text', gb.to_s
|
137
|
+
gb.put('zzzzz')
|
138
|
+
assert_equal 'texz', gb.to_s
|
139
|
+
end
|
140
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gap_buffer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- "Lourens Naud\xC3\xA9"
|
12
|
+
- Hsin Tsao
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-25 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: WIP implementation of a Gap Buffer for Ruby MRI.
|
22
|
+
email:
|
23
|
+
- lourens@methodmissing.com
|
24
|
+
- stsao@lazyhacker.com
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions:
|
28
|
+
- ext/gap_buffer/extconf.rb
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- ext/gap_buffer/extconf.rb
|
33
|
+
- ext/gap_buffer/gap_buffer.cpp
|
34
|
+
- ext/gap_buffer/gap_buffer.h
|
35
|
+
- ext/gap_buffer/rubymain.cpp
|
36
|
+
- test/test.rb
|
37
|
+
- test/test_gap_buffer.rb
|
38
|
+
- bench/gap_buffer.rb
|
39
|
+
- README
|
40
|
+
- Rakefile
|
41
|
+
- gap_buffer.gemspec
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/methodmissing/gap_buffer
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.3.7
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: WIP implementation of a Gap Buffer for Ruby MRI.
|
74
|
+
test_files: []
|
75
|
+
|