Initial revision
[nvi.git] / common / cut.c
blobe31f02551eece2ed5c5e9462c81a5bbc40b16d2d
1 /* cut.c */
3 /* Author:
4 * Steve Kirkendall
5 * 14407 SW Teal Blvd. #C
6 * Beaverton, OR 97005
7 * kirkenda@cs.pdx.edu
8 */
11 /* This file contains function which manipulate the cut buffers. */
13 #include "config.h"
14 #include "vi.h"
15 #if TURBOC
16 #include <process.h> /* needed for getpid */
17 #endif
18 #if TOS
19 #include <osbind.h>
20 #define rename(a,b) Frename(0,a,b)
21 #endif
23 # define NANONS 9 /* number of anonymous buffers */
25 static struct cutbuf
27 short *phys; /* pointer to an array of #s of BLKs containing text */
28 int nblks; /* number of blocks in phys[] array */
29 int start; /* offset into first block of start of cut */
30 int end; /* offset into last block of end of cut */
31 int tmpnum; /* ID number of the temp file */
32 char lnmode; /* boolean: line-mode cut? (as opposed to char-mode) */
34 named[27], /* cut buffers "a through "z and ". */
35 anon[NANONS]; /* anonymous cut buffers */
37 static char cbname; /* name chosen for next cut/paste operation */
38 static char dotcb; /* cut buffer to use if "doingdot" is set */
41 #ifndef NO_RECYCLE
42 /* This function builds a list of all blocks needed in the current tmp file
43 * for the contents of cut buffers.
44 * !!! WARNING: if you have more than ~450000 bytes of text in all of the
45 * cut buffers, then this will fail disastrously, because buffer overflow
46 * is *not* allowed for.
48 int cutneeds(need)
49 BLK *need; /* this is where we deposit the list */
51 struct cutbuf *cb; /* used to count through cut buffers */
52 int i; /* used to count through blocks of a cut buffer */
53 int n; /* total number of blocks in list */
55 n = 0;
57 /* first the named buffers... */
58 for (cb = named; cb < &named[27]; cb++)
60 if (cb->tmpnum != tmpnum)
61 continue;
63 for (i = cb->nblks; i-- > 0; )
65 need->n[n++] = cb->phys[i];
69 /* then the anonymous buffers */
70 for (cb = anon; cb < &anon[NANONS]; cb++)
72 if (cb->tmpnum != tmpnum)
73 continue;
75 for (i = cb->nblks; i-- > 0; )
77 need->n[n++] = cb->phys[i];
81 /* return the length of the list */
82 return n;
84 #endif
86 static void maybezap(num)
87 int num; /* the tmpnum of the temporary file to [maybe] delete */
89 char cutfname[80];
90 int i;
92 /* if this is the current tmp file, then we'd better keep it! */
93 if (tmpfd >= 0 && num == tmpnum)
95 return;
98 /* see if anybody else needs this tmp file */
99 for (i = 27; --i >= 0; )
101 if (named[i].nblks > 0 && named[i].tmpnum == num)
103 break;
106 if (i < 0)
108 for (i = NANONS; --i >= 0 ; )
110 if (anon[i].nblks > 0 && anon[i].tmpnum == num)
112 break;
117 /* if nobody else needs it, then discard the tmp file */
118 if (i < 0)
120 #if MSDOS || TOS
121 strcpy(cutfname, o_directory);
122 if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i - 1]))
123 cutfname[i++] = SLASH;
124 sprintf(cutfname + i, TMPNAME + 3, getpid(), num);
125 #else
126 sprintf(cutfname, TMPNAME, o_directory, getpid(), num);
127 #endif
128 unlink(cutfname);
132 /* This function frees a cut buffer. If it was the last cut buffer that
133 * refered to an old temp file, then it will delete the temp file. */
134 static void cutfree(buf)
135 struct cutbuf *buf;
137 int num;
139 /* return immediately if the buffer is already empty */
140 if (buf->nblks <= 0)
142 return;
145 /* else free up stuff */
146 num = buf->tmpnum;
147 buf->nblks = 0;
148 #ifdef DEBUG
149 if (!buf->phys)
150 msg("cutfree() tried to free a NULL buf->phys pointer.");
151 else
152 #endif
153 free((char *)buf->phys);
155 /* maybe delete the temp file */
156 maybezap(num);
159 /* This function is called when we are about to abort a tmp file.
161 * To minimize the number of extra files lying around, only named cut buffers
162 * are preserved in a file switch; the anonymous buffers just go away.
164 void cutswitch()
166 int i;
168 /* mark the current temp file as being "obsolete", and close it. */
169 storename((char *)0);
170 close(tmpfd);
171 tmpfd = -1;
173 /* discard all anonymous cut buffers */
174 for (i = 0; i < NANONS; i++)
176 cutfree(&anon[i]);
179 /* delete the temp file, if we don't really need it */
180 maybezap(tmpnum);
183 /* This function should be called just before termination of vi */
184 void cutend()
186 int i, num;
188 /* free the anonymous buffers, if they aren't already free */
189 cutswitch();
191 /* free all named cut buffers, since they might be forcing an older
192 * tmp file to be retained.
194 for (i = 0; i < 27; i++)
196 cutfree(&named[i]);
199 /* delete the temp file */
200 maybezap(tmpnum);
204 /* This function is used to select the cut buffer to be used next */
205 void cutname(name)
206 int name; /* a single character */
208 cbname = name;
214 /* This function copies a selected segment of text to a cut buffer */
215 void cut(from, to)
216 MARK from; /* start of text to cut */
217 MARK to; /* end of text to cut */
219 int first; /* logical number of first block in cut */
220 int last; /* logical number of last block used in cut */
221 long line; /* a line number */
222 int lnmode; /* boolean: will this be a line-mode cut? */
223 MARK delthru;/* end of text temporarily inserted for apnd */
224 REG struct cutbuf *cb;
225 REG long l;
226 REG int i;
227 REG char *scan;
228 char *blkc;
230 /* detect whether this must be a line-mode cut or char-mode cut */
231 if (markidx(from) == 0 && markidx(to) == 0)
232 lnmode = TRUE;
233 else
234 lnmode = FALSE;
236 /* by default, we don't "delthru" anything */
237 delthru = MARK_UNSET;
239 /* handle the "doingdot" quirks */
240 if (doingdot)
242 if (!cbname)
244 cbname = dotcb;
247 else if (cbname != '.')
249 dotcb = cbname;
252 /* decide which cut buffer to use */
253 if (!cbname)
255 /* free up the last anonymous cut buffer */
256 cutfree(&anon[NANONS - 1]);
258 /* shift the anonymous cut buffers */
259 for (i = NANONS - 1; i > 0; i--)
261 anon[i] = anon[i - 1];
264 /* use the first anonymous cut buffer */
265 cb = anon;
266 cb->nblks = 0;
268 else if (cbname >= 'a' && cbname <= 'z')
270 cb = &named[cbname - 'a'];
271 cutfree(cb);
273 #ifndef CRUNCH
274 else if (cbname >= 'A' && cbname <= 'Z')
276 cb = &named[cbname - 'A'];
277 if (cb->nblks > 0)
279 /* resolve linemode/charmode differences */
280 if (!lnmode && cb->lnmode)
282 from &= ~(BLKSIZE - 1);
283 if (markidx(to) != 0 || to == from)
285 to = to + BLKSIZE - markidx(to);
287 lnmode = TRUE;
290 /* insert the old cut-buffer before the new text */
291 mark[28] = to;
292 delthru = paste(from, FALSE, TRUE);
293 if (delthru == MARK_UNSET)
295 return;
297 delthru++;
298 to = mark[28];
300 cutfree(cb);
302 #endif /* not CRUNCH */
303 else if (cbname == '.')
305 cb = &named[26];
306 cutfree(cb);
308 else
310 msg("Invalid cut buffer name: \"%c", cbname);
311 dotcb = cbname = '\0';
312 return;
314 cbname = '\0';
315 cb->tmpnum = tmpnum;
317 /* detect whether we're doing a line mode cut */
318 cb->lnmode = lnmode;
320 /* ---------- */
322 /* Reporting... */
323 if (markidx(from) == 0 && markidx(to) == 0)
325 rptlines = markline(to) - markline(from);
326 rptlabel = "yanked";
329 /* ---------- */
331 /* make sure each block has a physical disk address */
332 blksync();
334 /* find the first block in the cut */
335 line = markline(from);
336 for (first = 1; line > lnum[first]; first++)
340 /* fetch text of the block containing that line */
341 blkc = scan = blkget(first)->c;
343 /* find the mark in the block */
344 for (l = lnum[first - 1]; ++l < line; )
346 while (*scan++ != '\n')
350 scan += markidx(from);
352 /* remember the offset of the start */
353 cb->start = scan - blkc;
355 /* ---------- */
357 /* find the last block in the cut */
358 line = markline(to);
359 for (last = first; line > lnum[last]; last++)
363 /* fetch text of the block containing that line */
364 if (last != first)
366 blkc = scan = blkget(last)->c;
368 else
370 scan = blkc;
373 /* find the mark in the block */
374 for (l = lnum[last - 1]; ++l < line; )
376 while (*scan++ != '\n')
380 if (markline(to) <= nlines)
382 scan += markidx(to);
385 /* remember the offset of the end */
386 cb->end = scan - blkc;
388 /* ------- */
390 /* remember the physical block numbers of all included blocks */
391 cb->nblks = last - first;
392 if (cb->end > 0)
394 cb->nblks++;
396 #ifdef lint
397 cb->phys = (short *)0;
398 #else
399 cb->phys = (short *)malloc((unsigned)(cb->nblks * sizeof(short)));
400 #endif
401 for (i = 0; i < cb->nblks; i++)
403 cb->phys[i] = hdr.n[first++];
406 #ifndef CRUNCH
407 /* if we temporarily inserted text for appending, then delete that
408 * text now -- before the user sees it.
410 if (delthru)
412 line = rptlines;
413 delete(from, delthru);
414 rptlines = line;
415 rptlabel = "yanked";
417 #endif /* not CRUNCH */
421 static void readcutblk(cb, blkno)
422 struct cutbuf *cb;
423 int blkno;
425 char cutfname[50];/* name of an old temp file */
426 int fd; /* either tmpfd or the result of open() */
427 #if MSDOS || TOS
428 int i;
429 #endif
431 /* decide which fd to use */
432 if (cb->tmpnum == tmpnum)
434 fd = tmpfd;
436 else
438 #if MSDOS || TOS
439 strcpy(cutfname, o_directory);
440 if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i-1]))
441 cutfname[i++]=SLASH;
442 sprintf(cutfname+i, TMPNAME+3, getpid(), cb->tmpnum);
443 #else
444 sprintf(cutfname, TMPNAME, o_directory, getpid(), cb->tmpnum);
445 #endif
446 fd = open(cutfname, O_RDONLY);
449 /* get the block */
450 lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0);
451 if (read(fd, tmpblk.c, (unsigned)BLKSIZE) != BLKSIZE)
453 msg("Error reading back from tmp file for pasting!");
456 /* close the fd, if it isn't tmpfd */
457 if (fd != tmpfd)
459 close(fd);
464 /* This function inserts text from a cut buffer, and returns the MARK where
465 * insertion ended. Return MARK_UNSET on errors.
467 MARK paste(at, after, retend)
468 MARK at; /* where to insert the text */
469 int after; /* boolean: insert after mark? (rather than before) */
470 int retend; /* boolean: return end of text? (rather than start) */
472 REG struct cutbuf *cb;
473 REG int i;
475 /* handle the "doingdot" quirks */
476 if (doingdot)
478 if (!cbname)
480 if (dotcb >= '1' && dotcb < '1' + NANONS - 1)
482 dotcb++;
484 cbname = dotcb;
487 else if (cbname != '.')
489 dotcb = cbname;
492 /* decide which cut buffer to use */
493 if (cbname >= 'A' && cbname <= 'Z')
495 cb = &named[cbname - 'A'];
497 else if (cbname >= 'a' && cbname <= 'z')
499 cb = &named[cbname - 'a'];
501 else if (cbname >= '1' && cbname <= '9')
503 cb = &anon[cbname - '1'];
505 else if (cbname == '.')
507 cb = &named[26];
509 else if (!cbname)
511 cb = anon;
513 else
515 msg("Invalid cut buffer name: \"%c", cbname);
516 cbname = '\0';
517 return MARK_UNSET;
520 /* make sure it isn't empty */
521 if (cb->nblks == 0)
523 if (cbname)
524 msg("Cut buffer \"%c is empty", cbname);
525 else
526 msg("Cut buffer is empty");
527 cbname = '\0';
528 return MARK_UNSET;
530 cbname = '\0';
532 /* adjust the insertion MARK for "after" and line-mode cuts */
533 if (cb->lnmode)
535 at &= ~(BLKSIZE - 1);
536 if (after)
538 at += BLKSIZE;
541 else if (after)
543 /* careful! if markidx(at) == 0 we might be pasting into an
544 * empty line -- so we can't blindly increment "at".
546 if (markidx(at) == 0)
548 pfetch(markline(at));
549 if (plen != 0)
551 at++;
554 else
556 at++;
560 /* put a copy of the "at" mark in the mark[] array, so it stays in
561 * sync with changes made via add().
563 mark[27] = at;
565 /* simple one-block paste? */
566 if (cb->nblks == 1)
568 /* get the block */
569 readcutblk(cb, 0);
571 /* isolate the text we need within it */
572 if (cb->end)
574 tmpblk.c[cb->end] = '\0';
577 /* insert it */
578 ChangeText
580 add(at, &tmpblk.c[cb->start]);
583 else
585 /* multi-block paste */
587 ChangeText
589 i = cb->nblks - 1;
591 /* add text from the last block first */
592 if (cb->end > 0)
594 readcutblk(cb, i);
595 tmpblk.c[cb->end] = '\0';
596 add(at, tmpblk.c);
597 i--;
600 /* add intervening blocks */
601 while (i > 0)
603 readcutblk(cb, i);
604 add(at, tmpblk.c);
605 i--;
608 /* add text from the first cut block */
609 readcutblk(cb, 0);
610 add(at, &tmpblk.c[cb->start]);
614 /* Reporting... */
615 rptlines = markline(mark[27]) - markline(at);
616 rptlabel = "pasted";
618 /* return the mark at the beginning/end of inserted text */
619 if (retend)
621 return mark[27] - 1L;
623 return at;
629 #ifndef NO_AT
631 /* This function copies characters from a cut buffer into a string.
632 * It returns the number of characters in the cut buffer. If the cut
633 * buffer is too large to fit in the string (i.e. if cb2str() returns
634 * a number >= size) then the characters will not have been copied.
635 * It returns 0 if the cut buffer is empty, and -1 for invalid cut buffers.
637 int cb2str(name, buf, size)
638 int name; /* the name of a cut-buffer to get: a-z only! */
639 char *buf; /* where to put the string */
640 unsigned size; /* size of buf */
642 REG struct cutbuf *cb;
643 REG char *src;
644 REG char *dest;
646 /* decide which cut buffer to use */
647 if (name >= 'a' && name <= 'z')
649 cb = &named[name - 'a'];
651 else
653 return -1;
656 /* if the buffer is empty, return 0 */
657 if (cb->nblks == 0)
659 return 0;
662 /* !!! if not a single-block cut, then fail */
663 if (cb->nblks != 1)
665 return size;
668 /* if too big, return the size now, without doing anything */
669 if (cb->end - cb->start >= size)
671 return cb->end - cb->start;
674 /* get the block */
675 readcutblk(cb, 0);
677 /* isolate the string within that blk */
678 if (cb->start == 0)
680 tmpblk.c[cb->end] = '\0';
682 else
684 for (dest = tmpblk.c, src = dest + cb->start; src < tmpblk.c + cb->end; )
686 *dest++ = *src++;
688 *dest = '\0';
691 /* copy the string into the buffer */
692 if (buf != tmpblk.c)
694 strcpy(buf, tmpblk.c);
697 /* return the length */
698 return cb->end - cb->start;
700 #endif