summaryrefslogtreecommitdiff
path: root/blk.c
diff options
context:
space:
mode:
Diffstat (limited to 'blk.c')
-rw-r--r--blk.c468
1 files changed, 468 insertions, 0 deletions
diff --git a/blk.c b/blk.c
new file mode 100644
index 0000000..6ece5dd
--- /dev/null
+++ b/blk.c
@@ -0,0 +1,468 @@
+/* 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
+
+extern long lseek();
+
+/*------------------------------------------------------------------------*/
+
+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);
+ }
+}
+
+/* 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, MODIFIED))
+ {
+ 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 */
+ changes++;
+
+ return TRUE;
+}