Commit | Line | Data |
---|---|---|
f5bcab4b WJ |
1 | /* cut.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 function which manipulate the cut buffers. */ | |
12 | ||
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 | |
22 | ||
23 | # define NANONS 9 /* number of anonymous buffers */ | |
24 | ||
25 | static struct cutbuf | |
26 | { | |
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) */ | |
33 | } | |
34 | named[27], /* cut buffers "a through "z and ". */ | |
35 | anon[NANONS]; /* anonymous cut buffers */ | |
36 | ||
37 | static char cbname; /* name chosen for next cut/paste operation */ | |
38 | static char dotcb; /* cut buffer to use if "doingdot" is set */ | |
39 | ||
40 | ||
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. | |
47 | */ | |
48 | int cutneeds(need) | |
49 | BLK *need; /* this is where we deposit the list */ | |
50 | { | |
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 */ | |
54 | ||
55 | n = 0; | |
56 | ||
57 | /* first the named buffers... */ | |
58 | for (cb = named; cb < &named[27]; cb++) | |
59 | { | |
60 | if (cb->tmpnum != tmpnum) | |
61 | continue; | |
62 | ||
63 | for (i = cb->nblks; i-- > 0; ) | |
64 | { | |
65 | need->n[n++] = cb->phys[i]; | |
66 | } | |
67 | } | |
68 | ||
69 | /* then the anonymous buffers */ | |
70 | for (cb = anon; cb < &anon[NANONS]; cb++) | |
71 | { | |
72 | if (cb->tmpnum != tmpnum) | |
73 | continue; | |
74 | ||
75 | for (i = cb->nblks; i-- > 0; ) | |
76 | { | |
77 | need->n[n++] = cb->phys[i]; | |
78 | } | |
79 | } | |
80 | ||
81 | /* return the length of the list */ | |
82 | return n; | |
83 | } | |
84 | #endif | |
85 | ||
86 | static void maybezap(num) | |
87 | int num; /* the tmpnum of the temporary file to [maybe] delete */ | |
88 | { | |
89 | char cutfname[80]; | |
90 | int i; | |
91 | ||
92 | /* if this is the current tmp file, then we'd better keep it! */ | |
93 | if (tmpfd >= 0 && num == tmpnum) | |
94 | { | |
95 | return; | |
96 | } | |
97 | ||
98 | /* see if anybody else needs this tmp file */ | |
99 | for (i = 27; --i >= 0; ) | |
100 | { | |
101 | if (named[i].nblks > 0 && named[i].tmpnum == num) | |
102 | { | |
103 | break; | |
104 | } | |
105 | } | |
106 | if (i < 0) | |
107 | { | |
108 | for (i = NANONS; --i >= 0 ; ) | |
109 | { | |
110 | if (anon[i].nblks > 0 && anon[i].tmpnum == num) | |
111 | { | |
112 | break; | |
113 | } | |
114 | } | |
115 | } | |
116 | ||
117 | /* if nobody else needs it, then discard the tmp file */ | |
118 | if (i < 0) | |
119 | { | |
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); | |
129 | } | |
130 | } | |
131 | ||
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; | |
136 | { | |
137 | int num; | |
138 | ||
139 | /* return immediately if the buffer is already empty */ | |
140 | if (buf->nblks <= 0) | |
141 | { | |
142 | return; | |
143 | } | |
144 | ||
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); | |
154 | ||
155 | /* maybe delete the temp file */ | |
156 | maybezap(num); | |
157 | } | |
158 | ||
159 | /* This function is called when we are about to abort a tmp file. | |
160 | * | |
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. | |
163 | */ | |
164 | void cutswitch() | |
165 | { | |
166 | int i; | |
167 | ||
168 | /* mark the current temp file as being "obsolete", and close it. */ | |
169 | storename((char *)0); | |
170 | close(tmpfd); | |
171 | tmpfd = -1; | |
172 | ||
173 | /* discard all anonymous cut buffers */ | |
174 | for (i = 0; i < NANONS; i++) | |
175 | { | |
176 | cutfree(&anon[i]); | |
177 | } | |
178 | ||
179 | /* delete the temp file, if we don't really need it */ | |
180 | maybezap(tmpnum); | |
181 | } | |
182 | ||
183 | /* This function should be called just before termination of vi */ | |
184 | void cutend() | |
185 | { | |
186 | int i; | |
187 | ||
188 | /* free the anonymous buffers, if they aren't already free */ | |
189 | cutswitch(); | |
190 | ||
191 | /* free all named cut buffers, since they might be forcing an older | |
192 | * tmp file to be retained. | |
193 | */ | |
194 | for (i = 0; i < 27; i++) | |
195 | { | |
196 | cutfree(&named[i]); | |
197 | } | |
198 | ||
199 | /* delete the temp file */ | |
200 | maybezap(tmpnum); | |
201 | } | |
202 | ||
203 | ||
204 | /* This function is used to select the cut buffer to be used next */ | |
205 | void cutname(name) | |
206 | int name; /* a single character */ | |
207 | { | |
208 | cbname = name; | |
209 | } | |
210 | ||
211 | ||
212 | ||
213 | ||
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 */ | |
218 | { | |
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; | |
229 | ||
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; | |
235 | ||
236 | /* by default, we don't "delthru" anything */ | |
237 | delthru = MARK_UNSET; | |
238 | ||
239 | /* handle the "doingdot" quirks */ | |
240 | if (doingdot) | |
241 | { | |
242 | if (!cbname) | |
243 | { | |
244 | cbname = dotcb; | |
245 | } | |
246 | } | |
247 | else if (cbname != '.') | |
248 | { | |
249 | dotcb = cbname; | |
250 | } | |
251 | ||
252 | /* decide which cut buffer to use */ | |
253 | if (!cbname) | |
254 | { | |
255 | /* free up the last anonymous cut buffer */ | |
256 | cutfree(&anon[NANONS - 1]); | |
257 | ||
258 | /* shift the anonymous cut buffers */ | |
259 | for (i = NANONS - 1; i > 0; i--) | |
260 | { | |
261 | anon[i] = anon[i - 1]; | |
262 | } | |
263 | ||
264 | /* use the first anonymous cut buffer */ | |
265 | cb = anon; | |
266 | cb->nblks = 0; | |
267 | } | |
268 | else if (cbname >= 'a' && cbname <= 'z') | |
269 | { | |
270 | cb = &named[cbname - 'a']; | |
271 | cutfree(cb); | |
272 | } | |
273 | #ifndef CRUNCH | |
274 | else if (cbname >= 'A' && cbname <= 'Z') | |
275 | { | |
276 | cb = &named[cbname - 'A']; | |
277 | if (cb->nblks > 0) | |
278 | { | |
279 | /* resolve linemode/charmode differences */ | |
280 | if (!lnmode && cb->lnmode) | |
281 | { | |
282 | from &= ~(BLKSIZE - 1); | |
283 | if (markidx(to) != 0 || to == from) | |
284 | { | |
285 | to = to + BLKSIZE - markidx(to); | |
286 | } | |
287 | lnmode = TRUE; | |
288 | } | |
289 | ||
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) | |
294 | { | |
295 | return; | |
296 | } | |
297 | delthru++; | |
298 | to = mark[28]; | |
299 | } | |
300 | cutfree(cb); | |
301 | } | |
302 | #endif /* not CRUNCH */ | |
303 | else if (cbname == '.') | |
304 | { | |
305 | cb = &named[26]; | |
306 | cutfree(cb); | |
307 | } | |
308 | else | |
309 | { | |
310 | msg("Invalid cut buffer name: \"%c", cbname); | |
311 | dotcb = cbname = '\0'; | |
312 | return; | |
313 | } | |
314 | cbname = '\0'; | |
315 | cb->tmpnum = tmpnum; | |
316 | ||
317 | /* detect whether we're doing a line mode cut */ | |
318 | cb->lnmode = lnmode; | |
319 | ||
320 | /* ---------- */ | |
321 | ||
322 | /* Reporting... */ | |
323 | if (markidx(from) == 0 && markidx(to) == 0) | |
324 | { | |
325 | rptlines = markline(to) - markline(from); | |
326 | rptlabel = "yanked"; | |
327 | } | |
328 | ||
329 | /* ---------- */ | |
330 | ||
331 | /* make sure each block has a physical disk address */ | |
332 | blksync(); | |
333 | ||
334 | /* find the first block in the cut */ | |
335 | line = markline(from); | |
336 | for (first = 1; line > lnum[first]; first++) | |
337 | { | |
338 | } | |
339 | ||
340 | /* fetch text of the block containing that line */ | |
341 | blkc = scan = blkget(first)->c; | |
342 | ||
343 | /* find the mark in the block */ | |
344 | for (l = lnum[first - 1]; ++l < line; ) | |
345 | { | |
346 | while (*scan++ != '\n') | |
347 | { | |
348 | } | |
349 | } | |
350 | scan += markidx(from); | |
351 | ||
352 | /* remember the offset of the start */ | |
353 | cb->start = scan - blkc; | |
354 | ||
355 | /* ---------- */ | |
356 | ||
357 | /* find the last block in the cut */ | |
358 | line = markline(to); | |
359 | for (last = first; line > lnum[last]; last++) | |
360 | { | |
361 | } | |
362 | ||
363 | /* fetch text of the block containing that line */ | |
364 | if (last != first) | |
365 | { | |
366 | blkc = scan = blkget(last)->c; | |
367 | } | |
368 | else | |
369 | { | |
370 | scan = blkc; | |
371 | } | |
372 | ||
373 | /* find the mark in the block */ | |
374 | for (l = lnum[last - 1]; ++l < line; ) | |
375 | { | |
376 | while (*scan++ != '\n') | |
377 | { | |
378 | } | |
379 | } | |
380 | if (markline(to) <= nlines) | |
381 | { | |
382 | scan += markidx(to); | |
383 | } | |
384 | ||
385 | /* remember the offset of the end */ | |
386 | cb->end = scan - blkc; | |
387 | ||
388 | /* ------- */ | |
389 | ||
390 | /* remember the physical block numbers of all included blocks */ | |
391 | cb->nblks = last - first; | |
392 | if (cb->end > 0) | |
393 | { | |
394 | cb->nblks++; | |
395 | } | |
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++) | |
402 | { | |
403 | cb->phys[i] = hdr.n[first++]; | |
404 | } | |
405 | ||
406 | #ifndef CRUNCH | |
407 | /* if we temporarily inserted text for appending, then delete that | |
408 | * text now -- before the user sees it. | |
409 | */ | |
410 | if (delthru) | |
411 | { | |
412 | line = rptlines; | |
413 | delete(from, delthru); | |
414 | rptlines = line; | |
415 | rptlabel = "yanked"; | |
416 | } | |
417 | #endif /* not CRUNCH */ | |
418 | } | |
419 | ||
420 | ||
421 | static void readcutblk(cb, blkno) | |
422 | struct cutbuf *cb; | |
423 | int blkno; | |
424 | { | |
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 | |
430 | ||
431 | /* decide which fd to use */ | |
432 | if (cb->tmpnum == tmpnum) | |
433 | { | |
434 | fd = tmpfd; | |
435 | } | |
436 | else | |
437 | { | |
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); | |
447 | } | |
448 | ||
449 | /* get the block */ | |
450 | lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0); | |
451 | if (read(fd, tmpblk.c, (unsigned)BLKSIZE) != BLKSIZE) | |
452 | { | |
453 | msg("Error reading back from tmp file for pasting!"); | |
454 | } | |
455 | ||
456 | /* close the fd, if it isn't tmpfd */ | |
457 | if (fd != tmpfd) | |
458 | { | |
459 | close(fd); | |
460 | } | |
461 | } | |
462 | ||
463 | ||
464 | /* This function inserts text from a cut buffer, and returns the MARK where | |
465 | * insertion ended. Return MARK_UNSET on errors. | |
466 | */ | |
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) */ | |
471 | { | |
472 | REG struct cutbuf *cb; | |
473 | REG int i; | |
474 | ||
475 | /* handle the "doingdot" quirks */ | |
476 | if (doingdot) | |
477 | { | |
478 | if (!cbname) | |
479 | { | |
480 | if (dotcb >= '1' && dotcb < '1' + NANONS - 1) | |
481 | { | |
482 | dotcb++; | |
483 | } | |
484 | cbname = dotcb; | |
485 | } | |
486 | } | |
487 | else if (cbname != '.') | |
488 | { | |
489 | dotcb = cbname; | |
490 | } | |
491 | ||
492 | /* decide which cut buffer to use */ | |
493 | if (cbname >= 'A' && cbname <= 'Z') | |
494 | { | |
495 | cb = &named[cbname - 'A']; | |
496 | } | |
497 | else if (cbname >= 'a' && cbname <= 'z') | |
498 | { | |
499 | cb = &named[cbname - 'a']; | |
500 | } | |
501 | else if (cbname >= '1' && cbname <= '9') | |
502 | { | |
503 | cb = &anon[cbname - '1']; | |
504 | } | |
505 | else if (cbname == '.') | |
506 | { | |
507 | cb = &named[26]; | |
508 | } | |
509 | else if (!cbname) | |
510 | { | |
511 | cb = anon; | |
512 | } | |
513 | else | |
514 | { | |
515 | msg("Invalid cut buffer name: \"%c", cbname); | |
516 | cbname = '\0'; | |
517 | return MARK_UNSET; | |
518 | } | |
519 | ||
520 | /* make sure it isn't empty */ | |
521 | if (cb->nblks == 0) | |
522 | { | |
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; | |
529 | } | |
530 | cbname = '\0'; | |
531 | ||
532 | /* adjust the insertion MARK for "after" and line-mode cuts */ | |
533 | if (cb->lnmode) | |
534 | { | |
535 | at &= ~(BLKSIZE - 1); | |
536 | if (after) | |
537 | { | |
538 | at += BLKSIZE; | |
539 | } | |
540 | } | |
541 | else if (after) | |
542 | { | |
543 | /* careful! if markidx(at) == 0 we might be pasting into an | |
544 | * empty line -- so we can't blindly increment "at". | |
545 | */ | |
546 | if (markidx(at) == 0) | |
547 | { | |
548 | pfetch(markline(at)); | |
549 | if (plen != 0) | |
550 | { | |
551 | at++; | |
552 | } | |
553 | } | |
554 | else | |
555 | { | |
556 | at++; | |
557 | } | |
558 | } | |
559 | ||
560 | /* put a copy of the "at" mark in the mark[] array, so it stays in | |
561 | * sync with changes made via add(). | |
562 | */ | |
563 | mark[27] = at; | |
564 | ||
565 | /* simple one-block paste? */ | |
566 | if (cb->nblks == 1) | |
567 | { | |
568 | /* get the block */ | |
569 | readcutblk(cb, 0); | |
570 | ||
571 | /* isolate the text we need within it */ | |
572 | if (cb->end) | |
573 | { | |
574 | tmpblk.c[cb->end] = '\0'; | |
575 | } | |
576 | ||
577 | /* insert it */ | |
578 | ChangeText | |
579 | { | |
580 | add(at, &tmpblk.c[cb->start]); | |
581 | } | |
582 | } | |
583 | else | |
584 | { | |
585 | /* multi-block paste */ | |
586 | ||
587 | ChangeText | |
588 | { | |
589 | i = cb->nblks - 1; | |
590 | ||
591 | /* add text from the last block first */ | |
592 | if (cb->end > 0) | |
593 | { | |
594 | readcutblk(cb, i); | |
595 | tmpblk.c[cb->end] = '\0'; | |
596 | add(at, tmpblk.c); | |
597 | i--; | |
598 | } | |
599 | ||
600 | /* add intervening blocks */ | |
601 | while (i > 0) | |
602 | { | |
603 | readcutblk(cb, i); | |
604 | add(at, tmpblk.c); | |
605 | i--; | |
606 | } | |
607 | ||
608 | /* add text from the first cut block */ | |
609 | readcutblk(cb, 0); | |
610 | add(at, &tmpblk.c[cb->start]); | |
611 | } | |
612 | } | |
613 | ||
614 | /* Reporting... */ | |
615 | rptlines = markline(mark[27]) - markline(at); | |
616 | rptlabel = "pasted"; | |
617 | ||
618 | /* return the mark at the beginning/end of inserted text */ | |
619 | if (retend) | |
620 | { | |
621 | return mark[27] - 1L; | |
622 | } | |
623 | return at; | |
624 | } | |
625 | ||
626 | ||
627 | ||
628 | ||
629 | #ifndef NO_AT | |
630 | ||
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. | |
636 | */ | |
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 */ | |
641 | { | |
642 | REG struct cutbuf *cb; | |
643 | REG char *src; | |
644 | REG char *dest; | |
645 | ||
646 | /* decide which cut buffer to use */ | |
647 | if (name >= 'a' && name <= 'z') | |
648 | { | |
649 | cb = &named[name - 'a']; | |
650 | } | |
651 | else | |
652 | { | |
653 | return -1; | |
654 | } | |
655 | ||
656 | /* if the buffer is empty, return 0 */ | |
657 | if (cb->nblks == 0) | |
658 | { | |
659 | return 0; | |
660 | } | |
661 | ||
662 | /* !!! if not a single-block cut, then fail */ | |
663 | if (cb->nblks != 1) | |
664 | { | |
665 | return size; | |
666 | } | |
667 | ||
668 | /* if too big, return the size now, without doing anything */ | |
669 | if (cb->end - cb->start >= size) | |
670 | { | |
671 | return cb->end - cb->start; | |
672 | } | |
673 | ||
674 | /* get the block */ | |
675 | readcutblk(cb, 0); | |
676 | ||
677 | /* isolate the string within that blk */ | |
678 | if (cb->start == 0) | |
679 | { | |
680 | tmpblk.c[cb->end] = '\0'; | |
681 | } | |
682 | else | |
683 | { | |
684 | for (dest = tmpblk.c, src = dest + cb->start; src < tmpblk.c + cb->end; ) | |
685 | { | |
686 | *dest++ = *src++; | |
687 | } | |
688 | *dest = '\0'; | |
689 | } | |
690 | ||
691 | /* copy the string into the buffer */ | |
692 | if (buf != tmpblk.c) | |
693 | { | |
694 | strcpy(buf, tmpblk.c); | |
695 | } | |
696 | ||
697 | /* return the length */ | |
698 | return cb->end - cb->start; | |
699 | } | |
700 | #endif |