This is buffer.c in view mode; [Download] [Up]
/* buffer.c */ /* Copyright 1995 by Steve Kirkendall */ char id_buffer[] = "$Id: buffer.c,v 2.68 1996/09/26 01:05:21 steve Exp $"; #include "elvis.h" #define swaplong(x,y) {long tmp; tmp = (x); (x) = (y); (y) = tmp;} #if USE_PROTOTYPES static void proc(_BLKNO_ bufinfo, long nchars, long nlines, long changes, long prevloc, CHAR *name); static void freeundo(BUFFER buffer); static void bufdo(BUFFER buf, BOOLEAN wipe); # ifdef DEBUG_ALLOC static void checkundo(char *where); static void removeundo(struct undo_s *undo); # endif #endif /* This variable points to the head of a linked list of buffers */ BUFFER buffers; /* This is the default buffer. Its options have been inserted into the * list accessible via optset(). This variable should only be changed by * the bufoptions() function. */ BUFFER bufdefault; /* This stores the message type that will be used for reporting the number * of lines read or written. It is normally MSG_STATUS so that other messages * will be allowed to overwrite it; however, when quitting it is set to * MSG_INFO so messages will be queued and eventually displayed somewhere * else after the window is closed. It is also set to MSG_INFO during * initialization so the "read..." message appears in the new window. */ MSGIMP bufmsgtype = MSG_INFO; /* This buffer's contents are irrelevent. The values of its options, though, * are significant because the values of its options are used as the default * values of any new buffer. This buffer is also used as the default buffer * during execution of the initialization scripts. */ BUFFER bufdefopts; /* This array describes buffer options */ static OPTDESC bdesc[] = { {"filename", "file", optsstring, optisstring }, {"bufname", "buffer", optsstring, optisstring }, {"buflines", "bl", optnstring, optisnumber }, {"bufchars", "bc", optnstring, optisnumber }, {"retain", "ret", NULL, NULL }, {"modified", "mod", NULL, NULL, }, {"edited", "samename", NULL, NULL, }, {"newfile", "new", NULL, NULL, }, {"readonly", "ro", NULL, NULL, }, {"autoindent", "ai", NULL, NULL, }, {"inputtab", "itab", opt1string, optisoneof, "tab spaces filename"}, {"autotab", "at", NULL, NULL, }, {"tabstop", "ts", optnstring, optisnumber, "1:100"}, {"ccprg", "cp", optsstring, optisstring }, {"equalprg", "ep", optsstring, optisstring }, {"keywordprg", "kp", optsstring, optisstring }, {"makeprg", "mp", optsstring, optisstring }, {"paragraphs", "para", optsstring, optisstring }, {"sections", "sect", optsstring, optisstring }, {"shiftwidth", "sw", optnstring, optisnumber }, {"undolevels", "ul", optnstring, optisnumber }, {"textwidth", "tw", optnstring, optisnumber }, {"internal", "internal",NULL, NULL }, {"bufdisplay", "bd", optsstring, optisstring }, {"errlines", "errlines",optnstring, optisnumber }, {"binary", "bin", NULL, NULL } }; #ifdef DEBUG_ALLOC /* This are used for maintaining a linked list of all undo versions. */ struct undo_s *undohead, *undotail; /* This function is called after code which is suspected of leaking memory. * It checks all of the undo versions, making sure that each one is still * accessible via some buffer. */ static void checkundo(where) char *where; { struct undo_s *scan, *undo; BUFFER buf; /* for each undo version... */ for (scan = undohead; scan; scan = scan->link1) { /* make sure the buffer still exists */ for (buf = buffers; buf != scan->buf; buf = buf->next) { if (!buf) msg(MSG_FATAL, "[s]$1 - buffer disappeared, undo/redo not freed", where); } /* make sure this is an undo/redo for this buffer */ if (scan->undoredo == 'l') { if (scan != buf->undolnptr) { msg(MSG_FATAL, "[s]$1 - undolnptr version leaked", where); } } else { for (undo = scan->undoredo=='u' ? buf->undo : buf->redo; undo != scan; undo = undo->next) { if (!undo) msg(MSG_FATAL, "[ss]$1 - $2 version leaked", where, scan->undoredo=='u'?"undo":"redo"); } } } } static void removeundo(undo) struct undo_s *undo; { if (undo->link1) undo->link1->link2 = undo->link2; else undotail = undo->link2; if (undo->link2) undo->link2->link1 = undo->link1; else undohead = undo->link1; } #else # define checkundo(s) #endif /* This function is called during session file initialization. It creates * a BUFFER struct for the buffer, and collects the undo versions. */ static void proc(bufinfo, nchars, nlines, changes, prevloc, name) _BLKNO_ bufinfo; /* block describing the buffer */ long nchars; /* number of characters in buffer */ long nlines; /* number of lines in buffer */ long changes; /* value of "changes" counter */ long prevloc; /* offset of most recent change to buffer */ CHAR *name; /* name of the buffer */ { BUFFER buf; BLKNO tmp; struct undo_s *undo, *scan, *lag; /* try to find a buffer by this name */ for (buf = buffers; buf && CHARcmp(o_bufname(buf), name); buf = buf->next) { } /* if no buffer exists yet, then create one and make it use the old * bufinfo block. */ if (!buf) { buf = bufalloc(name, bufinfo); buf->bufinfo = bufinfo; o_buflines(buf) = nlines; o_bufchars(buf) = nchars; buf->changes = changes; buf->changepos = prevloc; /* guess some values for a few other critical options */ if (!CHARncmp(name, toCHAR("Elvis "), 6) && CHARncmp(name, toCHAR("Elvis untitled"), 14)) { /* probably an internal buffer */ optpreset(o_internal(buf), True, OPT_HIDE); optpreset(o_modified(buf), False, OPT_HIDE); } else { /* the filename is probably the same as the buffer name */ optpreset(o_filename(buf), CHARdup(name), OPT_FREE|OPT_HIDE); optpreset(o_internal(buf), False, OPT_HIDE); /* Mark it as readonly so the user will have to think * before clobbering an existing file. */ o_readonly(buf) = True; /* Mark it as modified, so the user has to think * before exitting and losing this session file. */ optpreset(o_modified(buf), True, OPT_HIDE); } return; } /* We found the buffer. Is this the newest version found so far? */ if (changes > buf->changes) { /* yes, this is the newest. Swap this one with the version * currently in the buf struct. That will leave this version * (the newest) as the current version, and the current * (second newest) in the arguements and ready to be added * to the undo list. */ tmp = buf->bufinfo; buf->bufinfo = bufinfo; bufinfo = tmp; swaplong(o_buflines(buf), nlines); swaplong(o_bufchars(buf), nchars); swaplong(buf->changes, changes); swaplong(buf->changepos, prevloc); } /* insert as an "undo" version */ undo = (struct undo_s *)safealloc(1, sizeof *undo); undo->changes = changes; undo->changepos = prevloc; undo->buflines = nlines; undo->bufchars = nchars; undo->bufinfo = bufinfo; for (scan = buf->undo, lag = NULL; scan && scan->changes > changes; lag = scan, scan = scan->next) { } undo->next = scan; if (lag) { lag->next = undo; } else { buf->undo = undo; } #ifdef DEBUG_ALLOC undo->link1 = undohead; undohead = undo; undo->link2 = NULL; if (undo->link1) undo->link1->link2 = undo; else undotail = undo; undo->buf = buf; undo->undoredo = 'u'; #endif } /* Restart a session */ void bufinit() { assert(BUFOPTQTY == QTY(bdesc)); /* find any buffers left over from a previous edit */ lowinit(proc); /* create the default options buffer, if it doesn't exist already */ bufdefopts = bufalloc(toCHAR(DEFAULT_BUF), 0); o_internal(bufdefopts) = True; bufoptions(bufdefopts); } /* Create a buffer with a given name. The buffer will initially be empty; * if it is meant to be associated with a particular file, then the file must * be copied into the buffer in a separate operation. If there is already * a buffer with the desired name, it returns a pointer to the old buffer * instead of creating a new one. */ BUFFER bufalloc(name, bufinfo) CHAR *name; /* name of the buffer */ _BLKNO_ bufinfo;/* block number describing the buffer (0 to create) */ { BUFFER buffer; BUFFER scan, lag; /* used for inserting new buffer */ char unique[255]; /* name of untitled buffer */ int i = 1; /* for generating a name for untitled buffer */ /* if no name was specified, generate a unique untitled name */ if (!name) { do { sprintf(unique, UNTITLED_BUF, i++); } while (buffind(toCHAR(unique))); name = toCHAR(unique); } /* see if there's already a buffer with that name */ buffer = buffind(name); if (buffer) { return buffer; } /* allocate buffer struct */ buffer = (BUFFER)safekept(1, sizeof(*buffer)); /* create a low-level buffer */ if (bufinfo) { buffer->bufinfo = bufinfo; } else { buffer->bufinfo = lowalloc(tochar8(name)); } /* initialize the buffer options */ optpreset(o_readonly(buffer), o_defaultreadonly, OPT_HIDE); if (bufdefopts) { /* copy all options except the following: filename bufname * buflines bufchars modified edited newfile internal autotab */ optpreset(buffer->retain, bufdefopts->retain, OPT_HIDE); buffer->autoindent = bufdefopts->autoindent; buffer->inputtab = bufdefopts->inputtab; buffer->tabstop = bufdefopts->tabstop; buffer->shiftwidth = bufdefopts->shiftwidth; buffer->undolevels = bufdefopts->undolevels; buffer->textwidth = bufdefopts->textwidth; buffer->autotab = bufdefopts->autotab; /* Strings are tricky, because we may need to allocate a * duplicate of the value. */ buffer->cc = bufdefopts->cc; if (buffer->cc.flags & OPT_FREE) o_cc(buffer) = CHARdup(o_cc(bufdefopts)); buffer->cc.flags |= OPT_HIDE; buffer->equalprg = bufdefopts->equalprg; if (buffer->equalprg.flags & OPT_FREE) o_equalprg(buffer) = CHARdup(o_equalprg(bufdefopts)); buffer->keywordprg = bufdefopts->keywordprg; if (buffer->keywordprg.flags & OPT_FREE) o_keywordprg(buffer) = CHARdup(o_keywordprg(bufdefopts)); buffer->make = bufdefopts->make; if (buffer->make.flags & OPT_FREE) o_make(buffer) = CHARdup(o_make(bufdefopts)); buffer->make.flags |= OPT_HIDE; buffer->paragraphs = bufdefopts->paragraphs; if (buffer->paragraphs.flags & OPT_FREE) o_paragraphs(buffer) = CHARdup(o_paragraphs(bufdefopts)); buffer->sections = bufdefopts->sections; if (buffer->sections.flags & OPT_FREE) o_sections(buffer) = CHARdup(o_sections(bufdefopts)); buffer->bufdisplay = bufdefopts->bufdisplay; if (buffer->bufdisplay.flags & OPT_FREE) o_bufdisplay(buffer) = CHARdup(o_bufdisplay(bufdefopts)); } else /* no default options -- set them explicitly */ { o_inputtab(buffer) = 't'; o_autotab(buffer) = True; o_tabstop(buffer) = 8; #ifdef OSCCPRG o_cc(buffer) = toCHAR(OSCCPRG); #else o_cc(buffer) = toCHAR("cc ($1?$1:$2)"); #endif o_equalprg(buffer) = toCHAR("fmt"); o_keywordprg(buffer) = toCHAR("ref"); #ifdef OSMAKEPRG o_make(buffer) = toCHAR(OSMAKEPRG); #else o_make(buffer) = toCHAR("make $1"); #endif o_paragraphs(buffer) = toCHAR("PPppIPLPQPP"); o_sections(buffer) = toCHAR("NHSHSSSEse"); o_shiftwidth(buffer) = 8; o_undolevels(buffer) = 0; o_bufdisplay(buffer) = toCHAR("normal"); optflags(o_textwidth(buffer)) = OPT_REDRAW; } /* set the name of this buffer, and limit access to some options */ optpreset(o_bufname(buffer), CHARkdup(name), OPT_HIDE|OPT_LOCK|OPT_FREE); optflags(o_buflines(buffer)) = OPT_HIDE|OPT_LOCK; optflags(o_bufchars(buffer)) = OPT_HIDE|OPT_LOCK; /* Add the buffer to the linked list of buffers. Keep it sorted. */ for (lag = (BUFFER)0, scan = buffers; scan && CHARcmp(o_bufname(scan), o_bufname(buffer)) < 0; lag = scan, scan = scan->next) { } buffer->next = scan; if (lag) { lag->next = buffer; } else { buffers = buffer; } /* return the new buffer */ return buffer; } /* Locate a buffer with a particular name. (Note that this uses the buffer * name, not the filename.) Returns a pointer to the buffer, or NULL for * unknown buffers. */ BUFFER buffind(name) CHAR *name; /* name of the buffer to find */ { BUFFER buffer; /* scan through buffers, looking for a match */ for (buffer = buffers; buffer && CHARcmp(name, o_bufname(buffer)); buffer = buffer->next) { } return buffer; } /* Read a text file or filter output into a specific place in the buffer */ BOOLEAN bufread(mark, rfile) MARK mark; /* where to insert the new next */ char *rfile; /* file to read, or !cmd for filter */ { BUFFER buf; /* the buffer we're reading into */ long offset; /* offset of mark before inserting text */ long origlines; /* original number of lines in file */ CHAR chunk[4096]; /* I/O buffer */ int nread; /* number of bytes in chunk[] */ BOOLEAN newbuf; /* is this a new buffer? */ /* initialize some vars */ buf = markbuffer(mark); newbuf = (BOOLEAN)(o_bufchars(buf) == 0 && rfile[0] != '!' && (o_verbose || !o_internal(buf))); origlines = o_buflines(buf); /* open the file/filter */ if (!ioopen(rfile, 'r', False, False, o_binary(markbuffer(mark)))) { msg(MSG_ERROR, "[s]error opening $1", rfile); return False; } /* read the text */ if (newbuf) msg(MSG_STATUS, "[s]reading $1", rfile); while ((nread = ioread(chunk, QTY(chunk))) > 0) { if (guipoll(False)) { ioclose(); return False; } offset = markoffset(mark); bufreplace(mark, mark, chunk, nread); marksetoffset(mark, offset + nread); } ioclose(); if (newbuf) msg(bufmsgtype, "[sdd]read $1, $2 lines, $3 chars", rfile, o_buflines(buf), o_bufchars(buf)); else if (!o_internal(buf)) msg(bufmsgtype, "[d]read $1 lines", o_buflines(buf) - origlines); return True; } /* Create a buffer for a given file, and then load the file. Return a pointer * to the buffer. If the file can't be read for some reason, then complain and * leave the buffer empty, but still return the empty buffer. * * If the buffer already exists and contains text, then the "reload" option * can be used to force it to discard that text and reload the buffer; when * "reload" is False, it would leave the buffer unchanged instead. */ BUFFER bufload(bufname, filename, reload) CHAR *bufname; /* name of buffer, or NULL to derive from filename */ char *filename; /* name of file to load into a buffer */ BOOLEAN reload; /* load from file even if previously loaded? */ { BUFFER buf; MARKBUF top; MARKBUF end; BUFFER initbuf; /* buffer containing the initialization script */ int i; /* Create a buffer, whose name defaults to the same as this file */ buf = bufalloc(bufname ? bufname : toCHAR(filename), 0); /* Does the buffer already contain text? */ if (o_bufchars(buf) > 0) { /* If we aren't supposed to reload, then just return the * buffer as-is. */ if (!reload) return buf; /* Save the text as an "undo" version, and then delete it */ if (windefault && markbuffer(windefault->cursor) == buf) bufwilldo(windefault->cursor); else bufwilldo(marktmp(top, buf, 0)); bufreplace(marktmp(top, buf, 0), marktmp(end, buf, o_bufchars(buf)), (CHAR *)0, 0); } /* Set the buffer's options */ optpreset(o_filename(buf), CHARkdup(filename), OPT_HIDE|OPT_LOCK|OPT_FREE); optpreset(o_internal(buf), (bufname ? True : False), OPT_HIDE); optpreset(o_edited(buf), True, OPT_HIDE); o_readonly(buf) = False; optpreset(o_newfile(buf), False, OPT_HIDE); switch (dirperm(filename)) { case DIR_INVALID: case DIR_BADPATH: case DIR_NOTFILE: case DIR_UNREADABLE: case DIR_READONLY: o_readonly(buf) = True; break; case DIR_NEW: o_newfile(buf) = True; break; case DIR_READWRITE: /* nothing needed */ break; } /* Execute the "before read" script, if it exists. If the script * fails, then don't load the newly-created buffer. */ if (!o_internal(buf)) { initbuf = buffind(toCHAR(BEFOREREAD_BUF)); if (initbuf) { /* make the buffer available to :set */ bufoptions(buf); /* execute the script */ if (experform(windefault, marktmp(top, initbuf, 0), marktmp(end, initbuf, o_bufchars(initbuf))) != RESULT_COMPLETE) { return buf; } } } /* read the file's contents into the buffer */ if (o_newfile(buf)) { msg(bufmsgtype, "[sdd]$1 [NEW FILE]", filename); } else if (!bufread(marktmp(top, buf, 0), filename)) { o_edited(buf) = False; return buf; } /* set other options to describe the file */ o_modified(buf) = False; optpreset(o_errlines(buf), o_buflines(buf), OPT_HIDE); /* Restore the marks to their previous offsets. Otherwise any marks * which refer to this buffer will be set to the end of the file. * Restoring them isn't perfect, but it beats setting them all to EOF! * (Note: New buffers won't have an "undo" version.) */ if (buf->undo) { for (i = 0; i < QTY(buf->undo->offset); i++) { /* ignore unset marks, or marks into other buffers*/ if (buf->undo->offset[i] < 0) continue; assert(markbuffer(namedmark[i]) == buf); /* marks past the new end should be freed */ if (buf->undo->offset[i] >= o_bufchars(buf)) { markfree(namedmark[i]); continue; } /* other marks should have their offsets restored * to what they were before the buffer was loaded */ marksetoffset(namedmark[i], buf->undo->offset[i]); } } /* execute the file initialization script, if it exists */ if (!o_internal(buf)) { initbuf = buffind(toCHAR(AFTERREAD_BUF)); if (initbuf) { /* make the buffer available to :set */ bufoptions(buf); /* Execute the script's contents. */ (void)experform(windefault, marktmp(top, initbuf, 0), marktmp(end, initbuf, o_bufchars(initbuf))); } } return buf; } /* This function searches through a path for a file to load. It then loads * that file into a buffer and returns the buffer. If the file couldn't be * located, it returns NULL instead. If a buffer already exists with the given * name, then it returns that buffer without attempting to load anything. */ BUFFER bufpath(path, filename, bufname) CHAR *path; /* path to search through */ char *filename; /* file to search for */ CHAR *bufname; /* name of buffer to store the file */ { char *pathname; /* full pathname of the loaded file */ char pathdup[256]; /* local copy of pathname */ BUFFER buf; /* if the buffer already exists, return it immediately */ buf = buffind(bufname); if (buf) { return buf; } /* try to find the file */ pathname = iopath(tochar8(path), filename, False); if (!pathname) { return (BUFFER)0; } /* we need a local copy of the pathname, because bufload() will also * call iopath() to find the "elvis.brf" and "elvis.arf" files, and * iopath() only has a single static buffer that it uses for returning * the found pathname. We don't want our pathname clobbered. */ strcpy(pathdup, pathname); /* load the file */ buf = bufload(bufname, pathdup, True); return buf; } /* This function deletes the oldest undo versions of a given buffer */ static void freeundo(buffer) BUFFER buffer; /* buffer to be cleaned */ { struct undo_s *undo, *other, *tail; int i; checkundo("before freundo()"); /* locate the most recent doomed version */ i = o_undolevels(buffer); if (i < 1) i++; for (other = NULL, undo = buffer->undo; i > 0 && undo; i--, other = undo, undo = undo->next) { } /* if none are doomed, return now */ if (!undo) { return; } /* Remove the most recent doomed version (and all following versions) * from the linked list of undo versions. */ tail = other; if (tail) { tail->next = NULL; } else { buffer->undo = NULL; } /* delete each doomed version */ for (; undo; undo = other) { other = undo->next; /* free the lowbuf and the (struct undo_s) structure */ lowfree(undo->bufinfo); #ifdef DEBUG_ALLOC removeundo(undo); #endif safefree(undo); } checkundo("after freeundo"); } /* Free a buffer which was created via bufalloc(). */ void buffree(buffer) BUFFER buffer; /* buffer to be destroyed */ { BUFFER scan, lag; struct undo_s *undo; assert(buffer != bufdefopts); checkundo("before buffree"); /* if any window is editing this buffer, then fail */ if (wincount(buffer) > 0) { return; } /* transfer any marks to the dummy "bufdefopts" buffer */ while (buffer->marks) { marksetoffset(buffer->marks, 0L); marksetbuffer(buffer->marks, bufdefopts); } /* free any undo/redo versions of this buffer */ while (buffer->undo) { undo = buffer->undo; buffer->undo = undo->next; lowfree(undo->bufinfo); #ifdef DEBUG_ALLOC removeundo(undo); #endif safefree(undo); } while (buffer->redo) { undo = buffer->redo; buffer->redo = undo->next; lowfree(undo->bufinfo); #ifdef DEBUG_ALLOC removeundo(undo); #endif safefree(undo); } if (buffer->undolnptr) { lowfree(buffer->undolnptr->bufinfo); #ifdef DEBUG_ALLOC removeundo(buffer->undolnptr); #endif safefree(buffer->undolnptr); buffer->undolnptr = NULL; } assert(buffer->undo == NULL && buffer->redo == NULL); /* locate the buffer in the linked list */ for (lag = NULL, scan = buffers; scan != buffer; lag = scan, scan = scan->next) { assert(scan->next); } /* remove it from the linked list */ if (lag) { lag->next = scan->next; } else { buffers = scan->next; } /* free the values of any string options which have been set */ optfree(QTY(bdesc), &buffer->filename); /* free any marks in this buffer */ while (buffer->marks) { markfree(buffer->marks); } /* free the low-level block */ lowfree(buffer->bufinfo); /* free the buffer struct itself */ safefree(buffer); checkundo("after buffree"); } /* Free a buffer, if possible without losing anything important. Return * True if freed, False if retained. If "force" is True, it tries harder. */ BOOLEAN bufunload(buf, force, save) BUFFER buf; /* buffer to be unloaded */ BOOLEAN force; /* if True, try harder */ BOOLEAN save; /* if True, maybe save even if noautowrite */ { MARKBUF top, bottom; /* if "internal" then retain it for now */ if (o_internal(buf)) { return False; } /* if being used by some window, then retain it */ if (wincount(buf) > 0) { return False; } /* If supposed to retain, then keep it unless "force" is True. * If this is a temporary session, then no buffers will be retained, * so we should fail in that situation too. */ if (o_retain(buf) && !force && !o_tempsession) { return False; } /* if not modified, then discard it */ if (!o_modified(buf)) { buffree(buf); return True; } /* if readonly, or no known filename then free it if "force" or * retain it if not "force" */ if (o_readonly(buf) || o_filename(buf) == NULL) { if (force) { buffree(buf); } return force; } /* Try to save the buffer to a file */ if (save && o_filename(buf) && bufwrite(marktmp(top, buf, 0), marktmp(bottom, buf, o_bufchars(buf)), tochar8(o_filename(buf)), force)) { free(buf); return True; } return False; } /* Return True if "buf" can be deleted without loosing data, or False if it * can't -- in which case it also emits a message describing why. NOTE THAT * YOU ALSO NEED TO MAKE SURE THE BUFFER ISN'T BEING EDITED IN A WINDOW. * * This function has side-effects. If the buffer has been modified, it may * try to write the buffer out to a file. */ BOOLEAN bufsave(buf, force, mustwr) BUFFER buf; /* the buffer to write */ BOOLEAN force; /* passed to bufwrite() if writing is necessary */ BOOLEAN mustwr; /* write to file even if buffer isn't modified */ { MARKBUF top, bottom; /* Can never "save" or delete the internal buffers */ if (o_internal(buf)) { msg(MSG_ERROR, "[s]$1 is used internally by elvis", o_bufname(buf)); return False; } /* If writing wasn't explicitly demanded, and isn't needed, then * return True without doing anything else. */ if (!mustwr && !o_modified(buf)) { return True; } /* We know that we need to write this buffer. If it has no filename * then we can't write it. */ if (!o_filename(buf)) { msg(MSG_ERROR, "no file name"); return False; } /* try to write the buffer out to its file */ return bufwrite(marktmp(top, buf, 0), marktmp(bottom, buf, o_bufchars(buf)), tochar8(o_filename(buf)), force); } /* Write a buffer, or part of a buffer, out to a file. Return True if * successful, or False if error. */ BOOLEAN bufwrite(from, to, wfile, force) MARK from; /* start of text to write */ MARK to; /* end of text to write */ char *wfile; /* output file, ">>file" to append, "!cmd" to filter */ BOOLEAN force; /* write even if file already exists */ { BUFFER buf = markbuffer(from); BUFFER initbuf; /* one of the file initialization buffers */ MARKBUF top; /* the endpoints of initbuf */ MARKBUF next; /* used for determining append location */ CHAR *cp; /* used for scanning through file */ BOOLEAN append; /* If True, we're appending */ BOOLEAN filter; /* If True, we're writing to a filter */ BOOLEAN wholebuf; /* if True, we're writing the whole buffer */ BOOLEAN samefile; /* If True, we're writing the buffer to its original file */ int bytes; assert(from && to && wfile); assert(markbuffer(from) == markbuffer(to)); assert(markoffset(from) >= 0); assert(markoffset(from) <= markoffset(to)); assert(markoffset(to) <= o_bufchars(buf)); /* Determine some characteristics of this write */ append = (BOOLEAN)(wfile[0] == '>' && wfile[1] == '>'); filter = (BOOLEAN)(wfile[0] == '!'); wholebuf = (BOOLEAN)(markoffset(from) == 0 && markoffset(to) == o_bufchars(buf)); samefile = (BOOLEAN)(o_filename(buf) && !strcmp(wfile, tochar8(o_filename(buf)))); /* if we're appending, skip the initial ">>" */ if (append) { for (wfile += 2; isspace(*wfile); wfile++) { } } /* If writing to the same file, as it is a readonly file, then fail * unless we're forcing a write. */ if (!filter && wholebuf && samefile && o_readonly(buf) && !force) { msg(MSG_ERROR, "[s]$1 readonly" , wfile); return False; } /* If this is supposed to be a new file, or we're writing to * a name other than what was originally loaded, and we aren't * forcing a write, then make sure the file doesn't already * exist. */ if ((o_newfile(buf) || !o_edited(buf) || !samefile || !wholebuf) && !force && !append && !filter && dirperm(wfile) != DIR_NEW) { msg(MSG_ERROR, "[s]$1 exists", wfile); return False; } /* If we're writing the whole file back over itself, then execute the * "before write" script if it exists. If this fails and "force" isn't * true, then fail. */ if (wholebuf && samefile && !filter && !append) { initbuf = buffind(toCHAR(BEFOREWRITE_BUF)); if (initbuf) { /* make the buffer be the default buffer */ bufoptions(buf); /* execute the script */ if (experform(windefault, marktmp(top, initbuf, 0), marktmp(next, initbuf, o_bufchars(initbuf))) != RESULT_COMPLETE && !force) { return False; } } } /* Try to write the file */ if (ioopen(wfile, append ? 'a' : 'w', False, True, o_binary(markbuffer(from)))) { if (wholebuf && !filter) { msg(MSG_STATUS, "[s]writing $1", wfile); } next = *from; scanalloc(&cp, &next); assert(cp); bytes = 1; do { /* check for ^C */ if (guipoll(False)) { ioclose(); scanfree(&cp); return False; } bytes = scanright(&cp); if (markoffset(&next) + bytes > markoffset(to)) { bytes = (int)(markoffset(to) - markoffset(&next)); } if (iowrite(cp, bytes) < bytes) { msg(MSG_ERROR, (wfile[0] == '!') ? "broken pipe" : "disk full"); ioclose(); scanfree(&cp); return False; } markaddoffset(&next, bytes); scanseek(&cp, &next); } while (cp != NULL && markoffset(&next) < markoffset(to)); ioclose(); scanfree(&cp); if (!filter) { if (append) msg(bufmsgtype, "[ds]appended $1 lines to $2", markline(to) - markline(from), wfile); else msg(bufmsgtype, "[sdd]wrote $1, $2 lines, $3 chars", wfile, markline(to) - markline(from), markoffset(to) - markoffset(from)); } } else { /* We had an error, and already wrote the error message */ return False; } /* Execute the "after write" script. */ if (wholebuf && samefile && !filter && !append) { initbuf = buffind(toCHAR(AFTERWRITE_BUF)); if (initbuf) { /* make the buffer be the default buffer */ bufoptions(buf); /* execute the script */ (void)experform(windefault, marktmp(top, initbuf, 0), marktmp(next, initbuf, o_bufchars(initbuf))); } } /* Writing the whole file has some side-effects on options */ if (wholebuf && !append && !filter) { /* if it had no filename before, it has one now */ if (!o_filename(buf) && wholebuf) { o_filename(buf) = CHARdup(toCHAR(wfile)); optflags(o_filename(buf)) |= OPT_FREE; buftitle(buf, toCHAR(wfile)); } /* buffer is no longer modified */ o_modified(buf) = False; o_newfile(buf) = False; /* if the original file was overwritten, then reset the * readonly flag because apparently the file isn't readonly. */ if (!CHARcmp(o_filename(buf), toCHAR(wfile))) { o_readonly(buf) = False; o_edited(buf) = True; } } /* success! */ return True; } /* Make "buf" be the default buffer. The default buffer is the one whose * options are available to the :set command. */ void bufoptions(buf) BUFFER buf; /* the buffer to become the new default buffer */ { /* if same as before, then do nothing */ if (buf == bufdefault) { return; } /* if there is a previous buffer, then delete its options */ if (bufdefault) { optdelete(&bufdefault->filename); } /* make this buffer be the default */ bufdefault = buf; /* if bufdefault is not NULL, then insert its options */ if (buf) { optinsert("buf", QTY(bdesc), bdesc, &buf->filename); } } /* This function changes the name of a buffer. If the buffer happens to * be the main buffer of one or more windows, then it will also retitle * those windows. */ void buftitle(buffer, title) BUFFER buffer; /* the buffer whose name is to be changed */ CHAR *title; /* the new name of the buffer */ { WINDOW win; /* change the name */ safefree(o_bufname(buffer)); o_bufname(buffer) = CHARkdup(title); /* change the window titles, if the gui supports that */ if (gui->retitle) { for (win = winofbuf((WINDOW)0, buffer); win; win = winofbuf(win, buffer)) { (*gui->retitle)(win->gw, tochar8(o_bufname(buffer))); } } } /* Set the buffer's flag that will eventually cause an undo version of to * be saved. */ void bufwilldo(cursor) MARK cursor; /* where to put cursor if we return to this "undo" version */ { markbuffer(cursor)->willdo = True; markbuffer(cursor)->docursor = markoffset(cursor); } /* Save an "undo" version of the buffer that "cursor" points to */ static void bufdo(buf, wipe) BUFFER buf; /* buffer to make an "undo" version for */ BOOLEAN wipe; /* if True, then delete all "redo" versions */ { struct undo_s *undo; int i; long linenum; checkundo("before bufdo"); /* never save an undo version of an internal buffer */ if (o_internal(buf)) return; /* allocate an undo structure */ undo = (struct undo_s *)safealloc(1, sizeof *undo); #ifdef DEBUG_ALLOC undo->link1 = undohead; undohead = undo; undo->link2 = NULL; if (undo->link1) undo->link1->link2 = undo; else undotail = undo; undo->buf = buf; undo->undoredo = 'u'; #endif /* fill it in */ undo->changepos = buf->changepos = buf->docursor; undo->buflines = o_buflines(buf); undo->bufchars = o_bufchars(buf); for (i = 0; i < QTY(namedmark); i++) { if (namedmark[i] && markbuffer(namedmark[i]) == buf) { undo->offset[i] = markoffset(namedmark[i]); } else { undo->offset[i] = -1; } } undo->bufinfo = lowdup(buf->bufinfo); /* insert it into the buffer's "undo" list */ undo->next = buf->undo; buf->undo = undo; checkundo("in bufdo, before changing undolnptr"); /* If this is on a different line from previous change, then store this * as the current "line-undo". This is done by recursively calling * bufdo() with the same arguments to create a second identical * struct undo_s structure, and then moving the second one from the * undo list to the undolnptr variable. */ (void)lowoffset(undo->bufinfo, undo->changepos, (COUNT *)0, (COUNT *)0, (LBLKNO *)0, &linenum); if (linenum != buf->undoline) { /* change undoline to the expected line NOW, so we don't get * stuck in infinite recursion. */ buf->undoline = linenum; /* Call bufdo() again to make another copy of the undo version. * Increment undolevels temporarily so we don't loose the * oldest one yet. */ linenum = o_undolevels(buf); o_undolevels(buf)++; if (o_undolevels(buf) < 2) o_undolevels(buf) = 2; bufdo(buf, False); o_undolevels(buf) = linenum; /* free the old one, if any */ if (buf->undolnptr) { lowfree(buf->undolnptr->bufinfo); #ifdef DEBUG_ALLOC removeundo(buf->undolnptr); #endif safefree(buf->undolnptr); } /* Use the second undo copy as the line-undo version */ buf->undolnptr = buf->undo; buf->undo = buf->undo->next; #ifdef DEBUG_ALLOC buf->undolnptr->undoredo = 'l'; #endif assert(buf->undo == undo); } checkundo("in bufdo, after changing undolnptr"); /* discard the redo versions, if we're supposed to */ if (wipe) { while (buf->redo && buf->redo != buf->undolnptr) { undo = buf->redo; buf->redo = undo->next; lowfree(undo->bufinfo); #ifdef DEBUG_ALLOC removeundo(undo); #endif safefree(undo); } checkundo("in bufdo, after wiping the redo versions"); } /* discard the oldest version[s] from the undo list */ freeundo(buf); /* make sure this is written to disk */ if (!o_internal(buf) && eventcounter > 2) sessync(); } /* Recall a previous "undo" version of a given buffer. The "back" argument * should be positive to undo, negative to redo, or 0 to undo all changes * to the current line. Returns the cursor offset if successful, or -1 if * the requested undo version doesn't exist. */ long bufundo(cursor, back) MARK cursor; /* buffer to be undone, plus cursor offset of current version */ long back; /* number of versions back (negative to redo) */ { struct undo_s *undo; struct undo_s *tmp; long i; BUFFER buffer; MARKBUF from, to; long delta; long origulev; checkundo("before bufundo"); /* locate the desired undo version */ buffer = markbuffer(cursor); if (back == 0) { /* line undo */ undo = buffer->undolnptr; } else if (o_undolevels(buffer) == 0) { /* Can only oscillate between previous version and this one, * but it has the advantage that <u> and <^R> both do exactly * the same thing. */ if (buffer->redo) undo = buffer->redo, back = -1; else undo = buffer->undo, back = 1; } else if (back > 0) { /* undo */ for (i = back, undo = buffer->undo; undo && i > 1; i--, undo = undo->next) { } } else { /* redo */ for (i = -back, undo = buffer->redo; undo && i > 1; i--, undo = undo->next) { } } /* if the requested version doesn't exist, then fail */ if (!undo) { return -1; } /* save the current version as either an undo version or a redo version, * so we can revert to it. Note that we increase the number of undo * levels temporarily, so the oldest undo version won't be discarded * yet. */ origulev = o_undolevels(buffer); o_undolevels(buffer)++; if (o_undolevels(buffer) < 2) o_undolevels(buffer) = 2; bufwilldo(marktmp(from, buffer, undo->changepos)); bufdo(buffer, False); if (back > 0) { /* undoing: move from "undo" to "redo" */ while (buffer->undo != undo) { assert(buffer->undo); tmp = buffer->undo; buffer->undo = tmp->next; tmp->next = buffer->redo; buffer->redo = tmp; #ifdef DEBUG_ALLOC tmp->undoredo = 'r'; #endif } /* remove the selected version from the "undo" list */ buffer->undo = undo->next; } else if (back < 0) { /* redoing: move from "redo" to "undo" */ while (buffer->redo != undo) { assert(buffer->redo); tmp = buffer->redo; buffer->redo = tmp->next; tmp->next = buffer->undo; buffer->undo = tmp; #ifdef DEBUG_ALLOC tmp->undoredo = 'u'; #endif } /* remove the selected version from the "redo" list */ buffer->redo = undo->next; } else { /* line-undo: Remove the selected undo version from the * undolnptr pointer. */ buffer->undolnptr = (struct undo_s *)0; buffer->undoline = 0; } /* replace the current version with the selected undo version */ lowfree(buffer->bufinfo); buffer->bufinfo = undo->bufinfo; delta = undo->bufchars - o_bufchars(buffer); buffer->changepos = undo->changepos; o_buflines(buffer) = undo->buflines; o_bufchars(buffer) = undo->bufchars; buffer->changes++; o_modified(buffer) = True; /* Adjust the values of any marks. Most marks can be fixed just by * calling markadjust(), but the named marks' old values are stored in * the undo buffer and we can recall them exactly. */ if (delta < 0) { markadjust(marktmp(from, buffer, undo->changepos), marktmp(to, buffer, undo->changepos - delta), delta); } else { markadjust(marktmp(from, buffer, undo->changepos), &from, delta); } for (i = 0; i < QTY(namedmark); i++) { if (undo->offset[i] >= 0 && (!namedmark[i] || markbuffer(namedmark[i]) == buffer)) { if (namedmark[i]) markfree(namedmark[i]); namedmark[i] = markalloc(buffer, undo->offset[i]); } } #ifdef DISPLAY_MARKUP dmmuadjust(marktmp(from, buffer, 0), marktmp(to, buffer, o_bufchars(buffer)), 0); #endif /* We can free the undo_s structure now. */ #ifdef DEBUG_ALLOC removeundo(undo); #endif safefree(undo); /* Okay to free the oldest "undo" version now */ o_undolevels(buffer) = origulev; freeundo(buffer); /* Is this really necessary? */ /* Return the offset of the last change, so the cursor can be moved * there. Never return a point past the end of the buffer, though. */ checkundo("after bufundo"); if (o_bufchars(buffer) == 0) buffer->changepos = 0; else if (buffer->changepos >= o_bufchars(buffer)) buffer->changepos = o_bufchars(buffer) - 1; return buffer->changepos; } /* This function replaces part of a buffer with new text. In addition to * replacement, it can also be used to implement insertion (by having "from" * and "to" be identical) or deletion (by having "newlen" be zero). * * It uses markadjust() to automatically update marks. If the buffer's * "willdo" flag is set, then it will automatically create an "undo" version * of the buffer before making the change. */ void bufreplace(from, to, newp, newlen) MARK from; /* starting position of old text */ MARK to; /* ending position of old text */ CHAR *newp; /* pointer to new text (in RAM) */ long newlen; /* length of new text */ { long chglines; long chgchars; assert(markbuffer(from) == markbuffer(to) && newlen >= 0); /* if the destination's "willdo" flag is set, then save an "undo" * version of it before doing the change */ if (markbuffer(from)->willdo) { bufdo(markbuffer(from), True); markbuffer(from)->willdo = False; } /* make the change to the buffer contents */ if (markoffset(from) == markoffset(to)) { /* maybe we aren't really changing anything? */ if (newlen == 0) { return; } /* we're inserting */ chglines = lowinsert(markbuffer(from)->bufinfo, markoffset(from), newp, newlen); } else if (newlen == 0) { /* we're deleting */ chglines = lowdelete(markbuffer(from)->bufinfo, markoffset(from), markoffset(to)); } else { /* we're replacing */ chglines = lowreplace(markbuffer(from)->bufinfo, markoffset(from), markoffset(to), newp, newlen); } /* adjust the buffer totals */ chgchars = newlen - (markoffset(to) - markoffset(from)); o_buflines(markbuffer(from)) += chglines; o_bufchars(markbuffer(from)) += chgchars; o_modified(markbuffer(from)) = True; markbuffer(from)->changes++; /* adjust the marks */ #ifdef DISPLAY_MARKUP dmmuadjust(from, to, chgchars); #endif markadjust(from, to, chgchars); } /* Copy part of one buffer into another. "dst" is the destination (where * the pasted text will be inserted), and "from" and "to" describe the * portion of the source buffer to insert. * * This calls markadjust() to automatically adjust marks. If the destination * buffer's "willdo" flag is set, it will save an "undo" version before * making the change. */ void bufpaste(dst, from, to) MARK dst; /* destination */ MARK from; /* start of source */ MARK to; /* end of source */ { long chglines; long chgchars; assert(markbuffer(from) == markbuffer(to)); /* if the destination's "willdo" flag is set, then save an "undo" * version of it before doing the paste */ if (markbuffer(dst)->willdo) { bufdo(markbuffer(dst), True); markbuffer(dst)->willdo = False; } /* make the change to the buffer's contents */ chglines = lowpaste(markbuffer(dst)->bufinfo, markoffset(dst), markbuffer(from)->bufinfo, markoffset(from), markoffset(to)); /* adjust the destination's counters */ chgchars = markoffset(to) - markoffset(from); o_buflines(markbuffer(dst)) += chglines; o_bufchars(markbuffer(dst)) += chgchars; o_modified(markbuffer(dst)) = True; markbuffer(dst)->changes++; /* adjust marks */ markadjust(dst, dst, chgchars); } /* Copy a section of some buffer into dynamically-allocated RAM, and append * a NUL to the end of the copy. The calling function must call safefree() * on the returned memory. */ CHAR *bufmemory(from, to) MARK from, to; /* the section of the buffer to fetch */ { CHAR *memory; /* the allocated memory */ CHAR *scan, *build; /* used for copying text from buffer to memory */ long i; assert(markbuffer(from) == markbuffer(to) && markoffset(from) <= markoffset(to)); /* allocate space for the copy */ i = markoffset(to) - markoffset(from); memory = (CHAR *)safealloc((int)(i + 1), sizeof(CHAR)); /* copy the text into it */ for (scanalloc(&scan, from), build = memory; i > 0; scannext(&scan), i--) { assert(scan); *build++ = *scan; } scanfree(&scan); *build = '\0'; return memory; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.