aboutsummaryrefslogtreecommitdiff
path: root/libio/editbuf.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libio/editbuf.cc')
-rw-r--r--libio/editbuf.cc717
1 files changed, 717 insertions, 0 deletions
diff --git a/libio/editbuf.cc b/libio/editbuf.cc
new file mode 100644
index 00000000000..22304820f8f
--- /dev/null
+++ b/libio/editbuf.cc
@@ -0,0 +1,717 @@
+/* This is part of libio/iostream, providing -*- C++ -*- input/output.
+Copyright (C) 1993 Free Software Foundation
+
+This file is part of the GNU IO Library. This library is free
+software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this library; see the file COPYING. If not, write to the Free
+Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+As a special exception, if you link this library with files
+compiled with a GNU compiler to produce an executable, this does not cause
+the resulting executable to be covered by the GNU General Public License.
+This exception does not however invalidate any other reasons why
+the executable file might be covered by the GNU General Public License.
+
+Written by Per Bothner (bothner@cygnus.com). */
+
+#ifdef __GNUG__
+#pragma implementation
+#endif
+#include "libioP.h"
+#include "editbuf.h"
+#include <stddef.h>
+#include <stdlib.h>
+
+/* NOTE: Some of the code here is taken from GNU emacs */
+/* Hence this file falls under the GNU License! */
+
+// Invariants for edit_streambuf:
+// An edit_streambuf is associated with a specific edit_string,
+// which again is a sub-string of a specific edit_buffer.
+// An edit_streambuf is always in either get mode or put mode, never both.
+// In get mode, gptr() is the current position,
+// and pbase(), pptr(), and epptr() are all NULL.
+// In put mode, pptr() is the current position,
+// and eback(), gptr(), and egptr() are all NULL.
+// Any edit_streambuf that is actively doing insertion (as opposed to
+// replacing) // must have its pptr() pointing to the start of the gap.
+// Only one edit_streambuf can be actively inserting into a specific
+// edit_buffer; the edit_buffer's _writer field points to that edit_streambuf.
+// That edit_streambuf "owns" the gap, and the actual start of the
+// gap is the pptr() of the edit_streambuf; the edit_buffer::_gap_start pointer
+// will only be updated on an edit_streambuf::overflow().
+
+int edit_streambuf::truncate()
+{
+ str->buffer->delete_range(str->buffer->tell((buf_char*)pptr()),
+ str->buffer->tell(str->end));
+ return 0;
+}
+
+#ifdef OLD_STDIO
+inline void disconnect_gap_from_file(edit_buffer* buffer, FILE* fp)
+{
+ if (buffer->gap_start_ptr != &fp->__bufp)
+ return;
+ buffer->gap_start_normal = fp->__bufp;
+ buffer->gap_start_ptr = &buffer->gap_start_normal;
+}
+#endif
+
+void edit_streambuf::flush_to_buffer(edit_buffer* buffer)
+{
+ if (pptr() > buffer->_gap_start && pptr() < buffer->gap_end())
+ buffer->_gap_start = pptr();
+}
+
+void edit_streambuf::disconnect_gap_from_file(edit_buffer* buffer)
+{
+ if (buffer->_writer != this) return;
+ flush_to_buffer(buffer);
+ setp(pptr(),pptr());
+ buffer->_writer = NULL;
+}
+
+buf_index edit_buffer::tell(buf_char *ptr)
+{
+ if (ptr <= gap_start())
+ return ptr - data;
+ else
+ return ptr - gap_end() + size1();
+}
+
+#if 0
+buf_index buf_cookie::tell()
+{
+ return str->buffer->tell(file->__bufp);
+}
+#endif
+
+buf_index edit_buffer::tell(edit_mark*mark)
+{
+ return tell(data + mark->index_in_buffer(this));
+}
+
+// adjust the position of the gap
+
+void edit_buffer::move_gap(buf_offset pos)
+{
+ if (pos < size1())
+ gap_left (pos);
+ else if (pos > size1())
+ gap_right (pos);
+}
+
+void edit_buffer::gap_left (int pos)
+{
+ register buf_char *to, *from;
+ register int i;
+ int new_s1;
+
+ i = size1();
+ from = gap_start();
+ to = from + gap_size();
+ new_s1 = size1();
+
+ /* Now copy the characters. To move the gap down,
+ copy characters up. */
+
+ for (;;)
+ {
+ /* I gets number of characters left to copy. */
+ i = new_s1 - pos;
+ if (i == 0)
+ break;
+#if 0
+ /* If a quit is requested, stop copying now.
+ Change POS to be where we have actually moved the gap to. */
+ if (QUITP)
+ {
+ pos = new_s1;
+ break;
+ }
+#endif
+ /* Move at most 32000 chars before checking again for a quit. */
+ if (i > 32000)
+ i = 32000;
+ new_s1 -= i;
+ while (--i >= 0)
+ *--to = *--from;
+ }
+
+ /* Adjust markers, and buffer data structure, to put the gap at POS.
+ POS is where the loop above stopped, which may be what was specified
+ or may be where a quit was detected. */
+ adjust_markers (pos << 1, size1() << 1, gap_size(), data);
+#ifndef OLD_STDIO
+ _gap_start = data + pos;
+#else
+ if (gap_start_ptr == &gap_start_normal)
+ gap_start_normal = data + pos;
+#endif
+ __gap_end_pos = to - data;
+/* QUIT;*/
+}
+
+void edit_buffer::gap_right (int pos)
+{
+ register buf_char *to, *from;
+ register int i;
+ int new_s1;
+
+ i = size1();
+ to = gap_start();
+ from = i + gap_end();
+ new_s1 = i;
+
+ /* Now copy the characters. To move the gap up,
+ copy characters down. */
+
+ while (1)
+ {
+ /* I gets number of characters left to copy. */
+ i = pos - new_s1;
+ if (i == 0)
+ break;
+#if 0
+ /* If a quit is requested, stop copying now.
+ Change POS to be where we have actually moved the gap to. */
+ if (QUITP)
+ {
+ pos = new_s1;
+ break;
+ }
+#endif
+ /* Move at most 32000 chars before checking again for a quit. */
+ if (i > 32000)
+ i = 32000;
+ new_s1 += i;
+ while (--i >= 0)
+ *to++ = *from++;
+ }
+
+ adjust_markers ((size1() + gap_size()) << 1, (pos + gap_size()) << 1,
+ - gap_size(), data);
+#ifndef OLD_STDIO
+ _gap_start = data+pos;
+#else
+ if (gap_start_ptr == &gap_start_normal)
+ gap_start_normal = data + pos;
+#endif
+ __gap_end_pos = from - data;
+/* QUIT;*/
+}
+
+/* make sure that the gap in the current buffer is at least k
+ characters wide */
+
+void edit_buffer::make_gap(buf_offset k)
+{
+ register buf_char *p1, *p2, *lim;
+ buf_char *old_data = data;
+ int s1 = size1();
+
+ if (gap_size() >= k)
+ return;
+
+ /* Get more than just enough */
+ if (buf_size > 1000) k += 2000;
+ else k += /*200;*/ 20; // for testing!
+
+ p1 = (buf_char *) realloc (data, s1 + size2() + k);
+ if (p1 == 0)
+ abort(); /*memory_full ();*/
+
+ k -= gap_size(); /* Amount of increase. */
+
+ /* Record new location of text */
+ data = p1;
+
+ /* Transfer the new free space from the end to the gap
+ by shifting the second segment upward */
+ p2 = data + buf_size;
+ p1 = p2 + k;
+ lim = p2 - size2();
+ while (lim < p2)
+ *--p1 = *--p2;
+
+ /* Finish updating text location data */
+ __gap_end_pos += k;
+
+#ifndef OLD_STDIO
+ _gap_start = data + s1;
+#else
+ if (gap_start_ptr == &gap_start_normal)
+ gap_start_normal = data + s1;
+#endif
+
+ /* adjust markers */
+ adjust_markers (s1 << 1, (buf_size << 1) + 1, k, old_data);
+ buf_size += k;
+}
+
+/* Add `amount' to the position of every marker in the current buffer
+ whose current position is between `from' (exclusive) and `to' (inclusive).
+ Also, any markers past the outside of that interval, in the direction
+ of adjustment, are first moved back to the near end of the interval
+ and then adjusted by `amount'. */
+
+void edit_buffer::adjust_markers(register mark_pointer low,
+ register mark_pointer high,
+ int amount, buf_char *old_data)
+{
+ register struct edit_mark *m;
+ register mark_pointer mpos;
+ /* convert to mark_pointer */
+ amount <<= 1;
+
+ if (_writer)
+ _writer->disconnect_gap_from_file(this);
+
+ for (m = mark_list(); m != NULL; m = m->chain)
+ {
+ mpos = m->_pos;
+ if (amount > 0)
+ {
+ if (mpos > high && mpos < high + amount)
+ mpos = high + amount;
+ }
+ else
+ {
+ if (mpos > low + amount && mpos <= low)
+ mpos = low + amount;
+ }
+ if (mpos > low && mpos <= high)
+ mpos += amount;
+ m->_pos = mpos;
+ }
+
+ // Now adjust files
+ edit_streambuf *file;
+
+ for (file = files; file != NULL; file = file->next) {
+ mpos = file->current() - old_data;
+ if (amount > 0)
+ {
+ if (mpos > high && mpos < high + amount)
+ mpos = high + amount;
+ }
+ else
+ {
+ if (mpos > low + amount && mpos <= low)
+ mpos = low + amount;
+ }
+ if (mpos > low && mpos <= high)
+ mpos += amount;
+ char* new_pos = data + mpos;
+ file->set_current(new_pos, file->is_reading());
+ }
+}
+
+#if 0
+stdio_
+ __off == index at start of buffer (need only be valid after seek ? )
+ __buf ==
+
+if read/read_delete/overwrite mode:
+ __endp <= min(*gap_start_ptr, edit_string->end->ptr(buffer))
+
+if inserting:
+ must have *gap_start_ptr == __bufp && *gap_start_ptr+gap == __endp
+ file->edit_string->end->ptr(buffer) == *gap_start_ptr+end
+if write_mode:
+ if before gap
+#endif
+
+int edit_streambuf::underflow()
+{
+ if (!(_mode & ios::in))
+ return EOF;
+ struct edit_buffer *buffer = str->buffer;
+ if (!is_reading()) { // Must switch from put to get mode.
+ disconnect_gap_from_file(buffer);
+ set_current(pptr(), 1);
+ }
+ buf_char *str_end = str->end->ptr(buffer);
+ retry:
+ if (gptr() < egptr()) {
+ return *gptr();
+ }
+ if ((buf_char*)gptr() == str_end)
+ return EOF;
+ if (str_end <= buffer->gap_start()) {
+ setg(eback(), gptr(), str_end);
+ goto retry;
+ }
+ if (gptr() < buffer->gap_start()) {
+ setg(eback(), gptr(), buffer->gap_start());
+ goto retry;
+ }
+ if (gptr() == buffer->gap_start()) {
+ disconnect_gap_from_file(buffer);
+// fp->__offset += fp->__bufp - fp->__buffer;
+ setg(buffer->gap_end(), buffer->gap_end(), str_end);
+ }
+ else
+ setg(eback(), gptr(), str_end);
+ goto retry;
+}
+
+int edit_streambuf::overflow(int ch)
+{
+ if (_mode == ios::in)
+ return EOF;
+ struct edit_buffer *buffer = str->buffer;
+ flush_to_buffer(buffer);
+ if (ch == EOF)
+ return 0;
+ if (is_reading()) { // Must switch from get to put mode.
+ set_current(gptr(), 0);
+ }
+ buf_char *str_end = str->end->ptr(buffer);
+ retry:
+ if (pptr() < epptr()) {
+ *pptr() = ch;
+ pbump(1);
+ return (unsigned char)ch;
+ }
+ if ((buf_char*)pptr() == str_end || inserting()) {
+ /* insert instead */
+ if (buffer->_writer)
+ buffer->_writer->flush_to_buffer(); // Redundant?
+ buffer->_writer = NULL;
+ if (pptr() >= buffer->gap_end())
+ buffer->move_gap(pptr() - buffer->gap_size());
+ else
+ buffer->move_gap(pptr());
+ buffer->make_gap(1);
+ setp(buffer->gap_start(), buffer->gap_end());
+ buffer->_writer = this;
+ *pptr() = ch;
+ pbump(1);
+ return (unsigned char)ch;
+ }
+ if (str_end <= buffer->gap_start()) {
+ // Entire string is left of gap.
+ setp(pptr(), str_end);
+ }
+ else if (pptr() < buffer->gap_start()) {
+ // Current pos is left of gap.
+ setp(pptr(), buffer->gap_start());
+ goto retry;
+ }
+ else if (pptr() == buffer->gap_start()) {
+ // Current pos is at start of gap; move to end of gap.
+// disconnect_gap_from_file(buffer);
+ setp(buffer->gap_end(), str_end);
+// __offset += __bufp - __buffer;
+ }
+ else {
+ // Otherwise, current pos is right of gap.
+ setp(pptr(), str_end);
+ }
+ goto retry;
+}
+
+void edit_streambuf::set_current(char *new_pos, int reading)
+{
+ if (reading) {
+ setg(new_pos, new_pos, new_pos);
+ setp(NULL, NULL);
+ }
+ else {
+ setg(NULL, NULL, NULL);
+ setp(new_pos, new_pos);
+ }
+}
+
+// Called by fseek(fp, pos, whence) if fp is bound to a edit_buffer.
+
+streampos edit_streambuf::seekoff(streamoff offset, _seek_dir dir,
+ int /* =ios::in|ios::out*/)
+{
+ struct edit_buffer *buffer = str->buffer;
+ disconnect_gap_from_file(buffer);
+ buf_index cur_pos = buffer->tell((buf_char*)current());;
+ buf_index start_pos = buffer->tell(str->start);
+ buf_index end_pos = buffer->tell(str->end);
+ switch (dir) {
+ case ios::beg:
+ offset += start_pos;
+ break;
+ case ios::cur:
+ offset += cur_pos;
+ break;
+ case ios::end:
+ offset += end_pos;
+ break;
+ }
+ if (offset < start_pos || offset > end_pos)
+ return EOF;
+ buf_char *new_pos = buffer->data + offset;
+ buf_char* gap_start = buffer->gap_start();
+ if (new_pos > gap_start) {
+ buf_char* gap_end = buffer->gap_end();
+ new_pos += gap_end - gap_start;
+ if (new_pos >= buffer->data + buffer->buf_size) abort(); // Paranoia.
+ }
+ set_current(new_pos, is_reading());
+ return EOF;
+}
+
+#if 0
+int buf_seek(void *arg_cookie, fpos_t * pos, int whence)
+{
+ struct buf_cookie *cookie = arg_cookie;
+ FILE *file = cookie->file;
+ struct edit_buffer *buffer = cookie->str->buffer;
+ buf_char *str_start = cookie->str->start->ptr(buffer);
+ disconnect_gap_from_file(buffer, cookie->file);
+ fpos_t cur_pos, new_pos;
+ if (file->__bufp <= *buffer->gap_start_ptr
+ || str_start >= buffer->__gap_end)
+ cur_pos = str_start - file->__bufp;
+ else
+ cur_pos =
+ (*buffer->gap_start_ptr - str_start) + (file->__bufp - __gap_end);
+ end_pos = ...;
+ switch (whence) {
+ case SEEK_SET:
+ new_pos = *pos;
+ break;
+ case SEEK_CUR:
+ new_pos = cur_pos + *pos;
+ break;
+ case SEEK_END:
+ new_pos = end_pos + *pos;
+ break;
+ }
+ if (new_pos > end_pos) {
+ seek to end_pos;
+ insert_nulls(new_pos - end_pos);
+ return;
+ }
+ if (str_start + new_pos <= *gap_start_ptr &* *gap_start_ptr < end) {
+ __buffer = str_start;
+ __off = 0;
+ __bufp = str_start + new_pos;
+ file->__get_limit =
+ *buffer->gap_start_ptr; /* what if gap_start_ptr == &bufp ??? */
+ } else if () {
+
+ }
+ *pos = new_pos;
+}
+#endif
+
+/* Delete characters from `from' up to (but not incl) `to' */
+
+void edit_buffer::delete_range (buf_index from, buf_index to)
+{
+ register int numdel;
+
+ if ((numdel = to - from) <= 0)
+ return;
+
+ /* Make sure the gap is somewhere in or next to what we are deleting */
+ if (from > size1())
+ gap_right (from);
+ if (to < size1())
+ gap_left (to);
+
+ /* Relocate all markers pointing into the new, larger gap
+ to point at the end of the text before the gap. */
+ adjust_markers ((to + gap_size()) << 1, (to + gap_size()) << 1,
+ - numdel - gap_size(), data);
+
+ __gap_end_pos = to + gap_size();
+ _gap_start = data + from;
+}
+
+void edit_buffer::delete_range(struct edit_mark *start, struct edit_mark *end)
+{
+ delete_range(tell(start), tell(end));
+}
+
+void buf_delete_chars(struct edit_buffer *, struct edit_mark *, size_t)
+{
+ abort();
+}
+
+edit_streambuf::edit_streambuf(edit_string* bstr, int mode)
+{
+ _mode = mode;
+ str = bstr;
+ edit_buffer* buffer = bstr->buffer;
+ next = buffer->files;
+ buffer->files = this;
+ char* buf_ptr = bstr->start->ptr(buffer);
+ _inserting = 0;
+// setb(buf_ptr, buf_ptr, 0);
+ set_current(buf_ptr, !(mode & ios::out+ios::trunc+ios::app));
+ if (_mode & ios::trunc)
+ truncate();
+ if (_mode & ios::ate)
+ seekoff(0, ios::end);
+}
+
+// Called by fclose(fp) if fp is bound to a edit_buffer.
+
+#if 0
+static int buf_close(void *arg)
+{
+ register struct buf_cookie *cookie = arg;
+ struct edit_buffer *buffer = cookie->str->buffer;
+ struct buf_cookie **ptr;
+ for (ptr = &buffer->files; *ptr != cookie; ptr = &(*ptr)->next) ;
+ *ptr = cookie->next;
+ disconnect_gap_from_file(buffer, cookie->file);
+ free (cookie);
+ return 0;
+}
+#endif
+
+edit_streambuf::~edit_streambuf()
+{
+ if (_mode == ios::out)
+ truncate();
+ // Unlink this from list of files associated with bstr->buffer.
+ edit_streambuf **ptr = &str->buffer->files;
+ for (; *ptr != this; ptr = &(*ptr)->next) { }
+ *ptr = next;
+
+ disconnect_gap_from_file(str->buffer);
+}
+
+edit_buffer::edit_buffer()
+{
+ buf_size = /*200;*/ 15; /* for testing! */
+ data = (buf_char*)malloc(buf_size);
+ files = NULL;
+#ifndef OLD_STDIO
+ _gap_start = data;
+ _writer = NULL;
+#else
+ gap_start_normal = data;
+ gap_start_ptr = &gap_start_normal;
+#endif
+ __gap_end_pos = buf_size;
+ start_mark.chain = &end_mark;
+ start_mark._pos = 0;
+ end_mark.chain = NULL;
+ end_mark._pos = 2 * buf_size + 1;
+}
+
+// Allocate a new mark, which is adjusted by 'delta' bytes from 'this'.
+// Restrict new mark to lie within 'str'.
+
+edit_mark::edit_mark(struct edit_string *str, long delta)
+{
+ struct edit_buffer *buf = str->buffer;
+ chain = buf->start_mark.chain;
+ buf->start_mark.chain = this;
+ mark_pointer size1 = buf->size1() << 1;
+ int gap_size = buf->gap_size() << 1;
+ delta <<= 1;
+
+ // check if new and old marks are opposite sides of gap
+ if (_pos <= size1 && _pos + delta > size1)
+ delta += gap_size;
+ else if (_pos >= size1 + gap_size && _pos + delta < size1 + gap_size)
+ delta -= gap_size;
+
+ _pos = _pos + delta;
+ if (_pos < str->start->_pos & ~1)
+ _pos = (str->start->_pos & ~ 1) + (_pos & 1);
+ else if (_pos >= str->end->_pos)
+ _pos = (str->end->_pos & ~ 1) + (_pos & 1);
+}
+
+// A (slow) way to find the buffer a mark belongs to.
+
+edit_buffer * edit_mark::buffer()
+{
+ struct edit_mark *mark;
+ for (mark = this; mark->chain != NULL; mark = mark->chain) ;
+ // Assume that the last mark on the chain is the end_mark.
+ return (edit_buffer *)((char*)mark - offsetof(edit_buffer, end_mark));
+}
+
+edit_mark::~edit_mark()
+{
+ // Must unlink mark from chain of owning buffer
+ struct edit_buffer *buf = buffer();
+ if (this == &buf->start_mark || this == &buf->end_mark) abort();
+ edit_mark **ptr;
+ for (ptr = &buf->start_mark.chain; *ptr != this; ptr = &(*ptr)->chain) ;
+ *ptr = this->chain;
+}
+
+int edit_string::length() const
+{
+ ptrdiff_t delta = end->ptr(buffer) - start->ptr(buffer);
+ if (end->ptr(buffer) <= buffer->gap_start() ||
+ start->ptr(buffer) >= buffer->gap_end())
+ return delta;
+ return delta - buffer->gap_size();
+}
+
+buf_char * edit_string::copy_bytes(int *lenp) const
+{
+ char *new_str;
+ int len1, len2;
+ buf_char *start1, *start2;
+ start1 = start->ptr(buffer);
+ if (end->ptr(buffer) <= buffer->gap_start()
+ || start->ptr(buffer) >= buffer->gap_end()) {
+ len1 = end->ptr(buffer) - start1;
+ len2 = 0;
+ start2 = NULL; // To avoid a warning from g++.
+ }
+ else {
+ len1 = buffer->gap_start() - start1;
+ start2 = buffer->gap_end();
+ len2 = end->ptr(buffer) - start2;
+ }
+ new_str = (char*)malloc(len1 + len2 + 1);
+ memcpy(new_str, start1, len1);
+ if (len2 > 0) memcpy(new_str + len1, start2, len2);
+ new_str[len1+len2] = '\0';
+ *lenp = len1+len2;
+ return new_str;
+}
+
+// Replace the buf_chars in 'this' with ones from 'src'.
+// Equivalent to deleting this, then inserting src, except tries
+// to leave marks in place: Marks whose offset from the start
+// of 'this' is less than 'src->length()' will still have the
+// same offset in 'this' when done.
+
+void edit_string::assign(struct edit_string *src)
+{
+ edit_streambuf dst_file(this, ios::out);
+ if (buffer == src->buffer /*&& ???*/) { /* overly conservative */
+ int src_len;
+ buf_char *new_str;
+ new_str = src->copy_bytes(&src_len);
+ dst_file.sputn(new_str, src_len);
+ free (new_str);
+ } else {
+ edit_streambuf src_file(src, ios::in);
+ for ( ; ; ) {
+ int ch = src_file.sbumpc();
+ if (ch == EOF) break;
+ dst_file.sputc(ch);
+ }
+ }
+}