├── README.md ├── gap_buffer.cpp └── gap_buffer.h /README.md: -------------------------------------------------------------------------------- 1 | gapbuffer 2 | ========= 3 | 4 | C++ implementation of a gap buffer. 5 | 6 | The gap buffer is a technique used by text editors to store the entire text in a linear block of memory. It is a fairly simple technique that involves keeping track of 5 pointers and a sequencial block (gap) inside the buffer structure for inserting new text. The five pointers are (1) head of the buffer, (2) start of the gap, (3) first location outside the gap, (4) end of the buffer, and (5) location (point) within the buffer. The main rule for point is that it must be within the buffer and cannot be anywhere inside the gap other than the beginning of it. 7 | 8 | When text is to be written, the gap is moved to the point and new text can be written directly (each new character shrinks the gap by one). If there is no more room inside the gap, the gap is expanded. Deleting of a character involves moving the gap to the point and extending it to cover the deleted character. 9 | 10 | More details on this technique can be found in Craig Finseth's ["The Craft of Text Editing"](http://www.finseth.com/craft/) which is now available online, and the usenet postings of Joseph Allen. A set of the usenet postings (editech.X.Z) can be downloaded here: 11 | 12 | - [editech.1.Z](http://www.lazyhacker.com/editech.1.Z) 13 | - [editech.2.Z](http://www.lazyhacker.com/editech.2.Z) 14 | - [editech.3.Z](http://www.lazyhacker.com/editech.3.Z) 15 | - [editech.4.Z](http://www.lazyhacker.com/editech.4.Z) 16 | - [editech.5.Z](http://www.lazyhacker.com/editech.5.Z) 17 | 18 | Another very useful text is Ray Valdes' article, ["Text Editors: Algorithm and Architectures"](http://www.drdobbs.com/architecture-and-design/text-editors-algorithms-and-architecture/184408975), published in Dr. Dobb's Journal. 19 | 20 | What I've included is the source for a basic C++ implementation of gap buffer class which I hope will be useful to others. I don't have any restrictions on the use of this code other than that you include my name with any derived work. This is just my small contribution to the open source community and I hope to be able to offer more in the future, but for now I'm starting small. Any improvements, suggestions or advice are always appreciated. 21 | -------------------------------------------------------------------------------- /gap_buffer.cpp: -------------------------------------------------------------------------------- 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 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "gap_buffer.h" 32 | 33 | using namespace std; 34 | 35 | GapBuffer::GapBuffer(int gsize) : GAP_SIZE(gsize), buffer(NULL) 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 | } 312 | 313 | point++; 314 | 315 | // point should not be in the gap. 316 | if (point == gapstart) { 317 | point = gapend; 318 | } 319 | 320 | return *point; 321 | 322 | } 323 | 324 | void GapBuffer::PutChar(char ch) 325 | { 326 | InsertChar(ch); 327 | *point++; 328 | 329 | } 330 | 331 | /* 332 | * Insert character at point position. Note 333 | * that repeatedly calling this function will 334 | * keep calling MoveGapToPoint since this function 335 | * doesn't advance the point. The result is the 336 | * text appears reverse of key strokes. 337 | */ 338 | void GapBuffer::InsertChar(char ch) 339 | { 340 | // Here we do need to move the gap if the point 341 | // is not already at the start of the gap. 342 | 343 | if (point != gapstart) { 344 | MoveGapToPoint(); 345 | } 346 | 347 | // check to make sure that the gap has room 348 | if (gapstart == gapend) { 349 | ExpandGap(1); 350 | } 351 | 352 | *(gapstart++) = ch; 353 | 354 | } 355 | 356 | /* 357 | * Delete "size" number of characters. 358 | */ 359 | void GapBuffer::DeleteChars(unsigned int size) 360 | { 361 | 362 | if (point != gapstart) { 363 | MoveGapToPoint(); 364 | } 365 | 366 | // We shifted the gap so that gapend points to the location 367 | // where we want to start deleting so extend it 368 | // to cover all the characters. 369 | gapend += size; 370 | } 371 | 372 | void GapBuffer::InsertString(const char *string, unsigned int length) 373 | { 374 | 375 | MoveGapToPoint(); 376 | 377 | if (length > SizeOfGap()) { 378 | ExpandGap(length); 379 | } 380 | 381 | do { 382 | PutChar(*(string++)); 383 | } while ( length-- ); 384 | } 385 | 386 | /* 387 | * Here we initilize the buffer and set 388 | * the pointers to the correct position. 389 | */ 390 | int GapBuffer::InitBuffer(unsigned int size) 391 | { 392 | 393 | if (buffer) { 394 | free(buffer); 395 | } 396 | 397 | buffer = (char *) malloc(size); 398 | 399 | if (!buffer) { 400 | return 0; 401 | } 402 | 403 | point = buffer; 404 | gapstart = buffer; 405 | 406 | // initially gapend is outside of buffer 407 | gapend = buffer + size; 408 | bufend = gapend; 409 | 410 | return 1; 411 | 412 | } 413 | 414 | int GapBuffer::BufferSize() 415 | { 416 | return (bufend - buffer) - (gapend - gapstart); 417 | } 418 | 419 | /* 420 | char* GapBuffer::GetBuffer() 421 | { 422 | 423 | return buffer; 424 | } 425 | */ 426 | 427 | void GapBuffer::PrintBuffer() 428 | { 429 | /* 430 | char ch; 431 | 432 | cout << "Printing the buffer: " << endl; 433 | SetPoint(0); 434 | while (point < bufend) { 435 | cout << GetCharMovePoint(); 436 | } 437 | 438 | cout << "Printing the buffer in reverse: " << endl; 439 | 440 | while (point >= buffer) { 441 | cout << GetPrevCharMovePoint(); 442 | } 443 | */ 444 | 445 | char *temp = buffer; 446 | 447 | 448 | while (temp < bufend) { 449 | 450 | if ( (temp >= gapstart) && (temp < gapend) ) { 451 | cout << "_"; 452 | temp++; 453 | } else { 454 | cout << *(temp++); 455 | } 456 | 457 | } 458 | cout << endl; 459 | } 460 | 461 | int GapBuffer::SaveBufferToFile(FILE *file, unsigned int bytes) 462 | { 463 | 464 | if (!bytes) { 465 | return 1; 466 | } 467 | 468 | if (point == gapstart) { 469 | point = gapend; 470 | } 471 | 472 | if ( (gapstart > point) && (gapstart < (point + bytes)) && (gapstart != gapend) ) { 473 | if ( gapstart - point != fwrite(point, 1, gapstart-point, file) ) { 474 | return 0; 475 | } 476 | 477 | if ( (bytes - (gapstart - point)) != fwrite(gapend, 1, bytes-(gapstart - point), file) ) { 478 | return 1; 479 | } 480 | 481 | return 1; 482 | } else { 483 | return bytes == fwrite(point, 1, bytes, file); 484 | } 485 | 486 | 487 | } 488 | -------------------------------------------------------------------------------- /gap_buffer.h: -------------------------------------------------------------------------------- 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 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(const 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 | 158 | }; 159 | --------------------------------------------------------------------------------