// This is part of the iostream library, providing input/output for C++. // Copyright (C) 1991 Per Bothner. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, 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 // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #ifdef __GNUG__ #pragma implementation #endif #include "ioprivate.h" #include "editbuf.h" #include /* 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 mode /* =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 *buf, struct edit_mark *mark, size_t count) { 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); } } }