[9] | 1 | /* blk.c */
|
---|
| 2 |
|
---|
| 3 | /* Author:
|
---|
| 4 | * Steve Kirkendall
|
---|
| 5 | * 14407 SW Teal Blvd. #C
|
---|
| 6 | * Beaverton, OR 97005
|
---|
| 7 | * kirkenda@cs.pdx.edu
|
---|
| 8 | */
|
---|
| 9 |
|
---|
| 10 |
|
---|
| 11 | /* This file contains the functions that get/put blocks from the temp file.
|
---|
| 12 | * It also contains the "do" and "undo" functions.
|
---|
| 13 | */
|
---|
| 14 |
|
---|
| 15 | #include "config.h"
|
---|
| 16 | #include "vi.h"
|
---|
| 17 |
|
---|
| 18 | #ifndef NBUFS
|
---|
| 19 | # define NBUFS 5 /* must be at least 3 -- more is better */
|
---|
| 20 | #endif
|
---|
| 21 |
|
---|
| 22 |
|
---|
| 23 | /*------------------------------------------------------------------------*/
|
---|
| 24 |
|
---|
| 25 | BLK hdr; /* buffer for the header block */
|
---|
| 26 |
|
---|
| 27 | static int b4cnt; /* used to count context of beforedo/afterdo */
|
---|
| 28 | static struct _blkbuf
|
---|
| 29 | {
|
---|
| 30 | BLK buf; /* contents of a text block */
|
---|
| 31 | unsigned short logical; /* logical block number */
|
---|
| 32 | int dirty; /* must the buffer be rewritten? */
|
---|
| 33 | }
|
---|
| 34 | blk[NBUFS], /* buffers for text[?] blocks */
|
---|
| 35 | *toonew, /* buffer which shouldn't be recycled yet */
|
---|
| 36 | *newtoo, /* another buffer which should be recycled */
|
---|
| 37 | *recycle = blk; /* next block to be recycled */
|
---|
| 38 |
|
---|
| 39 |
|
---|
| 40 |
|
---|
| 41 |
|
---|
| 42 |
|
---|
| 43 | /* This function wipes out all buffers */
|
---|
| 44 | void blkinit()
|
---|
| 45 | {
|
---|
| 46 | int i;
|
---|
| 47 |
|
---|
| 48 | for (i = 0; i < NBUFS; i++)
|
---|
| 49 | {
|
---|
| 50 | blk[i].logical = 0;
|
---|
| 51 | blk[i].dirty = FALSE;
|
---|
| 52 | }
|
---|
| 53 | for (i = 0; i < MAXBLKS; i++)
|
---|
| 54 | {
|
---|
| 55 | hdr.n[i] = 0;
|
---|
| 56 | }
|
---|
| 57 | }
|
---|
| 58 |
|
---|
| 59 | /* This function allocates a buffer and fills it with a given block's text */
|
---|
| 60 | BLK *blkget(logical)
|
---|
| 61 | int logical; /* logical block number to fetch */
|
---|
| 62 | {
|
---|
| 63 | REG struct _blkbuf *this; /* used to step through blk[] */
|
---|
| 64 | REG int i;
|
---|
| 65 |
|
---|
| 66 | /* if logical is 0, just return the hdr buffer */
|
---|
| 67 | if (logical == 0)
|
---|
| 68 | {
|
---|
| 69 | return &hdr;
|
---|
| 70 | }
|
---|
| 71 |
|
---|
| 72 | /* see if we have that block in mem already */
|
---|
| 73 | for (this = blk; this < &blk[NBUFS]; this++)
|
---|
| 74 | {
|
---|
| 75 | if (this->logical == logical)
|
---|
| 76 | {
|
---|
| 77 | newtoo = toonew;
|
---|
| 78 | toonew = this;
|
---|
| 79 | return &this->buf;
|
---|
| 80 | }
|
---|
| 81 | }
|
---|
| 82 |
|
---|
| 83 | /* choose a block to be recycled */
|
---|
| 84 | do
|
---|
| 85 | {
|
---|
| 86 | this = recycle++;
|
---|
| 87 | if (recycle == &blk[NBUFS])
|
---|
| 88 | {
|
---|
| 89 | recycle = blk;
|
---|
| 90 | }
|
---|
| 91 | } while (this == toonew || this == newtoo);
|
---|
| 92 |
|
---|
| 93 | /* if it contains a block, flush that block */
|
---|
| 94 | blkflush(this);
|
---|
| 95 |
|
---|
| 96 | /* fill this buffer with the desired block */
|
---|
| 97 | this->logical = logical;
|
---|
| 98 | if (hdr.n[logical])
|
---|
| 99 | {
|
---|
| 100 | /* it has been used before - fill it from tmp file */
|
---|
| 101 | lseek(tmpfd, (long)hdr.n[logical] * (long)BLKSIZE, 0);
|
---|
| 102 | if (read(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE)
|
---|
| 103 | {
|
---|
| 104 | msg("Error reading back from tmp file!");
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 | else
|
---|
| 108 | {
|
---|
| 109 | /* it is new - zero it */
|
---|
| 110 | for (i = 0; i < BLKSIZE; i++)
|
---|
| 111 | {
|
---|
| 112 | this->buf.c[i] = 0;
|
---|
| 113 | }
|
---|
| 114 | }
|
---|
| 115 |
|
---|
| 116 | /* This isn't really a change, but it does potentially invalidate
|
---|
| 117 | * the kinds of shortcuts that the "changes" variable is supposed
|
---|
| 118 | * to protect us from... so count it as a change.
|
---|
| 119 | */
|
---|
| 120 | changes++;
|
---|
| 121 |
|
---|
| 122 | /* mark it as being "not dirty" */
|
---|
| 123 | this->dirty = 0;
|
---|
| 124 |
|
---|
| 125 | /* return it */
|
---|
| 126 | newtoo = toonew;
|
---|
| 127 | toonew = this;
|
---|
| 128 | return &this->buf;
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 |
|
---|
| 132 |
|
---|
| 133 | /* This function writes a block out to the temporary file */
|
---|
| 134 | void blkflush(this)
|
---|
| 135 | REG struct _blkbuf *this; /* the buffer to flush */
|
---|
| 136 | {
|
---|
| 137 | long seekpos; /* seek position of the new block */
|
---|
| 138 | unsigned short physical; /* physical block number */
|
---|
| 139 |
|
---|
| 140 | /* if its empty (an orphan blkadd() maybe?) then make it dirty */
|
---|
| 141 | if (this->logical && !*this->buf.c)
|
---|
| 142 | {
|
---|
| 143 | blkdirty(&this->buf);
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | /* if it's an empty buffer or a clean version is on disk, quit */
|
---|
| 147 | if (!this->logical || hdr.n[this->logical] && !this->dirty)
|
---|
| 148 | {
|
---|
| 149 | return;
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | /* find a free place in the file */
|
---|
| 153 | #ifndef NO_RECYCLE
|
---|
| 154 | seekpos = allocate();
|
---|
| 155 | lseek(tmpfd, seekpos, 0);
|
---|
| 156 | #else
|
---|
| 157 | seekpos = lseek(tmpfd, 0L, 2);
|
---|
| 158 | #endif
|
---|
| 159 | physical = seekpos / BLKSIZE;
|
---|
| 160 |
|
---|
| 161 | /* put the block there */
|
---|
| 162 | if (write(tmpfd, this->buf.c, (unsigned)BLKSIZE) != BLKSIZE)
|
---|
| 163 | {
|
---|
| 164 | msg("Trouble writing to tmp file");
|
---|
| 165 | }
|
---|
| 166 | this->dirty = FALSE;
|
---|
| 167 |
|
---|
| 168 | /* update the header so it knows we put it there */
|
---|
| 169 | hdr.n[this->logical] = physical;
|
---|
| 170 | }
|
---|
| 171 |
|
---|
| 172 |
|
---|
| 173 | /* This function sets a block's "dirty" flag or deletes empty blocks */
|
---|
| 174 | void blkdirty(bp)
|
---|
| 175 | BLK *bp; /* buffer returned by blkget() */
|
---|
| 176 | {
|
---|
| 177 | REG int i, j;
|
---|
| 178 | REG char *scan;
|
---|
| 179 | REG int k;
|
---|
| 180 |
|
---|
| 181 | /* find the buffer */
|
---|
| 182 | for (i = 0; i < NBUFS && bp != &blk[i].buf; i++)
|
---|
| 183 | {
|
---|
| 184 | }
|
---|
| 185 | #ifdef DEBUG
|
---|
| 186 | if (i >= NBUFS)
|
---|
| 187 | {
|
---|
| 188 | msg("blkdirty() called with unknown buffer at 0x%lx", bp);
|
---|
| 189 | return;
|
---|
| 190 | }
|
---|
| 191 | if (blk[i].logical == 0)
|
---|
| 192 | {
|
---|
| 193 | msg("blkdirty called with freed buffer");
|
---|
| 194 | return;
|
---|
| 195 | }
|
---|
| 196 | #endif
|
---|
| 197 |
|
---|
| 198 | /* if this block ends with line# INFINITY, then it must have been
|
---|
| 199 | * allocated unnecessarily during tmpstart(). Forget it.
|
---|
| 200 | */
|
---|
| 201 | if (lnum[blk[i].logical] == INFINITY)
|
---|
| 202 | {
|
---|
| 203 | #ifdef DEBUG
|
---|
| 204 | if (blk[i].buf.c[0])
|
---|
| 205 | {
|
---|
| 206 | msg("bkldirty called with non-empty extra BLK");
|
---|
| 207 | }
|
---|
| 208 | #endif
|
---|
| 209 | blk[i].logical = 0;
|
---|
| 210 | blk[i].dirty = FALSE;
|
---|
| 211 | return;
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | /* count lines in this block */
|
---|
| 215 | for (j = 0, scan = bp->c; *scan && scan < bp->c + BLKSIZE; scan++)
|
---|
| 216 | {
|
---|
| 217 | if (*scan == '\n')
|
---|
| 218 | {
|
---|
| 219 | j++;
|
---|
| 220 | }
|
---|
| 221 | }
|
---|
| 222 |
|
---|
| 223 | /* adjust lnum, if necessary */
|
---|
| 224 | k = blk[i].logical;
|
---|
| 225 | j += (lnum[k - 1] - lnum[k]);
|
---|
| 226 | if (j != 0)
|
---|
| 227 | {
|
---|
| 228 | nlines += j;
|
---|
| 229 | while (k < MAXBLKS && lnum[k] != INFINITY)
|
---|
| 230 | {
|
---|
| 231 | lnum[k++] += j;
|
---|
| 232 | }
|
---|
| 233 | }
|
---|
| 234 |
|
---|
| 235 | /* if it still has text, mark it as dirty */
|
---|
| 236 | if (*bp->c)
|
---|
| 237 | {
|
---|
| 238 | blk[i].dirty = TRUE;
|
---|
| 239 | }
|
---|
| 240 | else /* empty block, so delete it */
|
---|
| 241 | {
|
---|
| 242 | /* adjust the cache */
|
---|
| 243 | k = blk[i].logical;
|
---|
| 244 | for (j = 0; j < NBUFS; j++)
|
---|
| 245 | {
|
---|
| 246 | if (blk[j].logical >= k)
|
---|
| 247 | {
|
---|
| 248 | blk[j].logical--;
|
---|
| 249 | }
|
---|
| 250 | }
|
---|
| 251 |
|
---|
| 252 | /* delete it from hdr.n[] and lnum[] */
|
---|
| 253 | blk[i].logical = 0;
|
---|
| 254 | blk[i].dirty = FALSE;
|
---|
| 255 | while (k < MAXBLKS - 1)
|
---|
| 256 | {
|
---|
| 257 | hdr.n[k] = hdr.n[k + 1];
|
---|
| 258 | lnum[k] = lnum[k + 1];
|
---|
| 259 | k++;
|
---|
| 260 | }
|
---|
| 261 | hdr.n[MAXBLKS - 1] = 0;
|
---|
| 262 | lnum[MAXBLKS - 1] = INFINITY;
|
---|
| 263 | }
|
---|
| 264 | }
|
---|
| 265 |
|
---|
| 266 |
|
---|
| 267 | /* insert a new block into hdr, and adjust the cache */
|
---|
| 268 | BLK *blkadd(logical)
|
---|
| 269 | int logical; /* where to insert the new block */
|
---|
| 270 | {
|
---|
| 271 | REG int i;
|
---|
| 272 |
|
---|
| 273 | /* adjust hdr and lnum[] */
|
---|
| 274 | for (i = MAXBLKS - 1; i > logical; i--)
|
---|
| 275 | {
|
---|
| 276 | hdr.n[i] = hdr.n[i - 1];
|
---|
| 277 | lnum[i] = lnum[i - 1];
|
---|
| 278 | }
|
---|
| 279 | hdr.n[logical] = 0;
|
---|
| 280 | lnum[logical] = lnum[logical - 1];
|
---|
| 281 |
|
---|
| 282 | /* adjust the cache */
|
---|
| 283 | for (i = 0; i < NBUFS; i++)
|
---|
| 284 | {
|
---|
| 285 | if (blk[i].logical >= logical)
|
---|
| 286 | {
|
---|
| 287 | blk[i].logical++;
|
---|
| 288 | }
|
---|
| 289 | }
|
---|
| 290 |
|
---|
| 291 | /* return the new block, via blkget() */
|
---|
| 292 | return blkget(logical);
|
---|
| 293 | }
|
---|
| 294 |
|
---|
| 295 |
|
---|
| 296 | /* This function forces all dirty blocks out to disk */
|
---|
| 297 | void blksync()
|
---|
| 298 | {
|
---|
| 299 | int i;
|
---|
| 300 |
|
---|
| 301 | for (i = 0; i < NBUFS; i++)
|
---|
| 302 | {
|
---|
| 303 | /* blk[i].dirty = TRUE; */
|
---|
| 304 | blkflush(&blk[i]);
|
---|
| 305 | }
|
---|
| 306 | if (*o_sync)
|
---|
| 307 | {
|
---|
| 308 | sync();
|
---|
| 309 | }
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | /*------------------------------------------------------------------------*/
|
---|
| 313 |
|
---|
| 314 | static MARK undocurs; /* where the cursor should go if undone */
|
---|
| 315 | static long oldnlines;
|
---|
| 316 | static long oldlnum[MAXBLKS];
|
---|
| 317 |
|
---|
| 318 |
|
---|
| 319 | /* This function should be called before each command that changes the text.
|
---|
| 320 | * It defines the state that undo() will reset the file to.
|
---|
| 321 | */
|
---|
| 322 | void beforedo(forundo)
|
---|
| 323 | int forundo; /* boolean: is this for an undo? */
|
---|
| 324 | {
|
---|
| 325 | REG int i;
|
---|
| 326 | REG long l;
|
---|
| 327 |
|
---|
| 328 | /* if this is a nested call to beforedo, quit! Use larger context */
|
---|
| 329 | if (b4cnt++ > 0)
|
---|
| 330 | {
|
---|
| 331 | return;
|
---|
| 332 | }
|
---|
| 333 |
|
---|
| 334 | /* force all block buffers to disk */
|
---|
| 335 | blksync();
|
---|
| 336 |
|
---|
| 337 | #ifndef NO_RECYCLE
|
---|
| 338 | /* perform garbage collection on blocks from tmp file */
|
---|
| 339 | garbage();
|
---|
| 340 | #endif
|
---|
| 341 |
|
---|
| 342 | /* force the header out to disk */
|
---|
| 343 | lseek(tmpfd, 0L, 0);
|
---|
| 344 | if (write(tmpfd, hdr.c, (unsigned)BLKSIZE) != BLKSIZE)
|
---|
| 345 | {
|
---|
| 346 | msg("Trouble writing header to tmp file ");
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | /* copy or swap oldnlines <--> nlines, oldlnum <--> lnum */
|
---|
| 350 | if (forundo)
|
---|
| 351 | {
|
---|
| 352 | for (i = 0; i < MAXBLKS; i++)
|
---|
| 353 | {
|
---|
| 354 | l = lnum[i];
|
---|
| 355 | lnum[i] = oldlnum[i];
|
---|
| 356 | oldlnum[i] = l;
|
---|
| 357 | }
|
---|
| 358 | l = nlines;
|
---|
| 359 | nlines = oldnlines;
|
---|
| 360 | oldnlines = l;
|
---|
| 361 | }
|
---|
| 362 | else
|
---|
| 363 | {
|
---|
| 364 | for (i = 0; i < MAXBLKS; i++)
|
---|
| 365 | {
|
---|
| 366 | oldlnum[i] = lnum[i];
|
---|
| 367 | }
|
---|
| 368 | oldnlines = nlines;
|
---|
| 369 | }
|
---|
| 370 |
|
---|
| 371 | /* save the cursor position */
|
---|
| 372 | undocurs = cursor;
|
---|
| 373 |
|
---|
| 374 | /* upon return, the calling function continues and makes changes... */
|
---|
| 375 | }
|
---|
| 376 |
|
---|
| 377 | /* This function marks the end of a (nested?) change to the file */
|
---|
| 378 | void afterdo()
|
---|
| 379 | {
|
---|
| 380 | if (--b4cnt)
|
---|
| 381 | {
|
---|
| 382 | /* after abortdo(), b4cnt may decribe nested beforedo/afterdo
|
---|
| 383 | * pairs incorrectly. If it is decremented to often, then
|
---|
| 384 | * keep b4cnt sane but don't do anything else.
|
---|
| 385 | */
|
---|
| 386 | if (b4cnt < 0)
|
---|
| 387 | b4cnt = 0;
|
---|
| 388 |
|
---|
| 389 | return;
|
---|
| 390 | }
|
---|
| 391 |
|
---|
| 392 | /* make sure the cursor wasn't left stranded in deleted text */
|
---|
| 393 | if (markline(cursor) > nlines)
|
---|
| 394 | {
|
---|
| 395 | cursor = MARK_LAST;
|
---|
| 396 | }
|
---|
| 397 | /* NOTE: it is still possible that markidx(cursor) is after the
|
---|
| 398 | * end of a line, so the Vi mode will have to take care of that
|
---|
| 399 | * itself */
|
---|
| 400 |
|
---|
| 401 | /* if a significant change has been made to this file, then set the
|
---|
| 402 | * MODIFIED flag.
|
---|
| 403 | */
|
---|
| 404 | if (significant)
|
---|
| 405 | {
|
---|
| 406 | setflag(file, MODIFIED);
|
---|
| 407 | setflag(file, UNDOABLE);
|
---|
| 408 | }
|
---|
| 409 | }
|
---|
| 410 |
|
---|
| 411 | /* This function cuts short the current set of changes. It is called after
|
---|
| 412 | * a SIGINT.
|
---|
| 413 | */
|
---|
| 414 | void abortdo()
|
---|
| 415 | {
|
---|
| 416 | /* finish the operation immediately. */
|
---|
| 417 | if (b4cnt > 0)
|
---|
| 418 | {
|
---|
| 419 | b4cnt = 1;
|
---|
| 420 | afterdo();
|
---|
| 421 | }
|
---|
| 422 |
|
---|
| 423 | /* in visual mode, the screen is probably screwed up */
|
---|
| 424 | if (mode == MODE_COLON)
|
---|
| 425 | {
|
---|
| 426 | mode = MODE_VI;
|
---|
| 427 | }
|
---|
| 428 | if (mode == MODE_VI)
|
---|
| 429 | {
|
---|
| 430 | redraw(MARK_UNSET, FALSE);
|
---|
| 431 | }
|
---|
| 432 | }
|
---|
| 433 |
|
---|
| 434 | /* This function discards all changes made since the last call to beforedo() */
|
---|
| 435 | int undo()
|
---|
| 436 | {
|
---|
| 437 | BLK oldhdr;
|
---|
| 438 |
|
---|
| 439 | /* if beforedo() has never been run, fail */
|
---|
| 440 | if (!tstflag(file, UNDOABLE))
|
---|
| 441 | {
|
---|
| 442 | msg("You haven't modified this file yet.");
|
---|
| 443 | return FALSE;
|
---|
| 444 | }
|
---|
| 445 |
|
---|
| 446 | /* read the old header form the tmp file */
|
---|
| 447 | lseek(tmpfd, 0L, 0);
|
---|
| 448 | if (read(tmpfd, oldhdr.c, (unsigned)BLKSIZE) != BLKSIZE)
|
---|
| 449 | {
|
---|
| 450 | msg("Trouble rereading the old header from tmp file");
|
---|
| 451 | }
|
---|
| 452 |
|
---|
| 453 | /* "do" the changed version, so we can undo the "undo" */
|
---|
| 454 | cursor = undocurs;
|
---|
| 455 | beforedo(TRUE);
|
---|
| 456 | afterdo();
|
---|
| 457 |
|
---|
| 458 | /* wipe out the block buffers - we can't assume they're correct */
|
---|
| 459 | blkinit();
|
---|
| 460 |
|
---|
| 461 | /* use the old header -- and therefore the old text blocks */
|
---|
| 462 | hdr = oldhdr;
|
---|
| 463 |
|
---|
| 464 | /* This is a change */
|
---|
| 465 | significant = TRUE;
|
---|
| 466 | changes++;
|
---|
| 467 |
|
---|
| 468 | return TRUE;
|
---|
| 469 | }
|
---|