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