/* blk.c */ /* Author: * Steve Kirkendall * 14407 SW Teal Blvd. #C * Beaverton, OR 97005 * kirkenda@cs.pdx.edu */ /* This file contains the functions that get/put blocks from the temp file. * It also contains the "do" and "undo" functions. */ #include "config.h" #include "vi.h" #ifndef NBUFS # define NBUFS 5 /* must be at least 3 -- more is better */ #endif /*------------------------------------------------------------------------*/ BLK hdr; /* buffer for the header block */ static int b4cnt; /* used to count context of beforedo/afterdo */ static struct _blkbuf { BLK buf; /* contents of a text block */ unsigned short logical; /* logical block number */ int dirty; /* must the buffer be rewritten? */ } blk[NBUFS], /* buffers for text[?] blocks */ *toonew, /* buffer which shouldn't be recycled yet */ *newtoo, /* another buffer which should be recycled */ *recycle = blk; /* next block to be recycled */ /* This function wipes out all buffers */ void blkinit() { int i; for (i = 0; i < NBUFS; i++) { blk[i].logical = 0; blk[i].dirty = FALSE; } for (i = 0; i < MAXBLKS; i++) { hdr.n[i] = 0; } } /* This function allocates a buffer and fills it with a given block's text */ BLK *blkget(logical) int logical; /* logical block number to fetch */ { REG struct _blkbuf *this; /* used to step through blk[] */ REG int i; /* if logical is 0, just return the hdr buffer */ if (logical == 0) { return &hdr; } /* see if we have that block in mem already */ for (this = blk; this < &blk[NBUFS]; this++) { if (this->logical == logical) { newtoo = toonew; toonew = this; return &this->buf; } } /* choose a block to be recycled */ do { this = recycle++; if (recycle == &blk[NBUFS]) { recycle = blk; } } while (this == toonew || this == newtoo); /* if it contains a block, flush that block */ blkflush(this); /* fill this buffer with the desired block */ this->logical = logical; if (hdr.n[logical]) { /* it has been used before - fill it from tmp file */ lseek(tmpfd, (long)hdr.n[logical] * (long)BLKSIZE, 0); if (read(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE) { msg("Error reading back from tmp file!"); } } else { /* it is new - zero it */ for (i = 0; i < BLKSIZE; i++) { this->buf.c[i] = 0; } } /* This isn't really a change, but it does potentially invalidate * the kinds of shortcuts that the "changes" variable is supposed * to protect us from... so count it as a change. */ changes++; /* mark it as being "not dirty" */ this->dirty = 0; /* return it */ newtoo = toonew; toonew = this; return &this->buf; } /* This function writes a block out to the temporary file */ void blkflush(this) REG struct _blkbuf *this; /* the buffer to flush */ { long seekpos; /* seek position of the new block */ unsigned short physical; /* physical block number */ /* if its empty (an orphan blkadd() maybe?) then make it dirty */ if (this->logical && !*this->buf.c) { blkdirty(&this->buf); } /* if it's an empty buffer or a clean version is on disk, quit */ if (!this->logical || hdr.n[this->logical] && !this->dirty) { return; } /* find a free place in the file */ #ifndef NO_RECYCLE seekpos = allocate(); lseek(tmpfd, seekpos, 0); #else seekpos = lseek(tmpfd, 0L, 2); #endif physical = seekpos / BLKSIZE; /* put the block there */ if (write(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE) { msg("Trouble writing to tmp file"); } this->dirty = FALSE; /* update the header so it knows we put it there */ hdr.n[this->logical] = physical; } /* This function sets a block's "dirty" flag or deletes empty blocks */ void blkdirty(bp) BLK *bp; /* buffer returned by blkget() */ { REG int i, j; REG char *scan; REG int k; /* find the buffer */ for (i = 0; i < NBUFS && bp != &blk[i].buf; i++) { } #ifdef DEBUG if (i >= NBUFS) { msg("blkdirty() called with unknown buffer at 0x%lx", bp); return; } if (blk[i].logical == 0) { msg("blkdirty called with freed buffer"); return; } #endif /* if this block ends with line# INFINITY, then it must have been * allocated unnecessarily during tmpstart(). Forget it. */ if (lnum[blk[i].logical] == INFINITY) { #ifdef DEBUG if (blk[i].buf.c[0]) { msg("bkldirty called with non-empty extra BLK"); } #endif blk[i].logical = 0; blk[i].dirty = FALSE; return; } /* count lines in this block */ for (j = 0, scan = bp->c; *scan && scan < bp->c + BLKSIZE; scan++) { if (*scan == '\n') { j++; } } /* adjust lnum, if necessary */ k = blk[i].logical; j += (lnum[k - 1] - lnum[k]); if (j != 0) { nlines += j; while (k < MAXBLKS && lnum[k] != INFINITY) { lnum[k++] += j; } } /* if it still has text, mark it as dirty */ if (*bp->c) { blk[i].dirty = TRUE; } else /* empty block, so delete it */ { /* adjust the cache */ k = blk[i].logical; for (j = 0; j < NBUFS; j++) { if (blk[j].logical >= k) { blk[j].logical--; } } /* delete it from hdr.n[] and lnum[] */ blk[i].logical = 0; blk[i].dirty = FALSE; while (k < MAXBLKS - 1) { hdr.n[k] = hdr.n[k + 1]; lnum[k] = lnum[k + 1]; k++; } hdr.n[MAXBLKS - 1] = 0; lnum[MAXBLKS - 1] = INFINITY; } } /* insert a new block into hdr, and adjust the cache */ BLK *blkadd(logical) int logical; /* where to insert the new block */ { REG int i; /* adjust hdr and lnum[] */ for (i = MAXBLKS - 1; i > logical; i--) { hdr.n[i] = hdr.n[i - 1]; lnum[i] = lnum[i - 1]; } hdr.n[logical] = 0; lnum[logical] = lnum[logical - 1]; /* adjust the cache */ for (i = 0; i < NBUFS; i++) { if (blk[i].logical >= logical) { blk[i].logical++; } } /* return the new block, via blkget() */ return blkget(logical); } /* This function forces all dirty blocks out to disk */ void blksync() { int i; for (i = 0; i < NBUFS; i++) { /* blk[i].dirty = TRUE; */ blkflush(&blk[i]); } if (*o_sync) { sync(); } } /*------------------------------------------------------------------------*/ static MARK undocurs; /* where the cursor should go if undone */ static long oldnlines; static long oldlnum[MAXBLKS]; /* This function should be called before each command that changes the text. * It defines the state that undo() will reset the file to. */ void beforedo(forundo) int forundo; /* boolean: is this for an undo? */ { REG int i; REG long l; /* if this is a nested call to beforedo, quit! Use larger context */ if (b4cnt++ > 0) { return; } /* force all block buffers to disk */ blksync(); #ifndef NO_RECYCLE /* perform garbage collection on blocks from tmp file */ garbage(); #endif /* force the header out to disk */ lseek(tmpfd, 0L, 0); if (write(tmpfd, hdr.c, (unsigned)BLKSIZE) != BLKSIZE) { msg("Trouble writing header to tmp file "); } /* copy or swap oldnlines <--> nlines, oldlnum <--> lnum */ if (forundo) { for (i = 0; i < MAXBLKS; i++) { l = lnum[i]; lnum[i] = oldlnum[i]; oldlnum[i] = l; } l = nlines; nlines = oldnlines; oldnlines = l; } else { for (i = 0; i < MAXBLKS; i++) { oldlnum[i] = lnum[i]; } oldnlines = nlines; } /* save the cursor position */ undocurs = cursor; /* upon return, the calling function continues and makes changes... */ } /* This function marks the end of a (nested?) change to the file */ void afterdo() { if (--b4cnt) { /* after abortdo(), b4cnt may decribe nested beforedo/afterdo * pairs incorrectly. If it is decremented to often, then * keep b4cnt sane but don't do anything else. */ if (b4cnt < 0) b4cnt = 0; return; } /* make sure the cursor wasn't left stranded in deleted text */ if (markline(cursor) > nlines) { cursor = MARK_LAST; } /* NOTE: it is still possible that markidx(cursor) is after the * end of a line, so the Vi mode will have to take care of that * itself */ /* if a significant change has been made to this file, then set the * MODIFIED flag. */ if (significant) { setflag(file, MODIFIED); setflag(file, UNDOABLE); } } /* This function cuts short the current set of changes. It is called after * a SIGINT. */ void abortdo() { /* finish the operation immediately. */ if (b4cnt > 0) { b4cnt = 1; afterdo(); } /* in visual mode, the screen is probably screwed up */ if (mode == MODE_COLON) { mode = MODE_VI; } if (mode == MODE_VI) { redraw(MARK_UNSET, FALSE); } } /* This function discards all changes made since the last call to beforedo() */ int undo() { BLK oldhdr; /* if beforedo() has never been run, fail */ if (!tstflag(file, UNDOABLE)) { msg("You haven't modified this file yet."); return FALSE; } /* read the old header form the tmp file */ lseek(tmpfd, 0L, 0); if (read(tmpfd, oldhdr.c, (unsigned)BLKSIZE) != BLKSIZE) { msg("Trouble rereading the old header from tmp file"); } /* "do" the changed version, so we can undo the "undo" */ cursor = undocurs; beforedo(TRUE); afterdo(); /* wipe out the block buffers - we can't assume they're correct */ blkinit(); /* use the old header -- and therefore the old text blocks */ hdr = oldhdr; /* This is a change */ significant = TRUE; changes++; return TRUE; }