Commit | Line | Data |
---|---|---|
1e64b3ba JH |
1 | /*- |
2 | * Copyright (c) 1992, 1993 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
15 | * This product includes software developed by the University of | |
16 | * California, Berkeley and its contributors. | |
17 | * 4. Neither the name of the University nor the names of its contributors | |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | #ifndef lint | |
178c26c5 | 35 | static char sccsid[] = "@(#)cut.c 8.20 (Berkeley) 1/23/94"; |
1e64b3ba JH |
36 | #endif /* not lint */ |
37 | ||
38 | #include <sys/types.h> | |
39 | ||
40 | #include <ctype.h> | |
41 | #include <errno.h> | |
42 | #include <fcntl.h> | |
43 | #include <stdlib.h> | |
44 | #include <string.h> | |
45 | ||
46 | #include "vi.h" | |
47 | ||
48 | static int cb_line __P((SCR *, EXF *, recno_t, size_t, size_t, TEXT **)); | |
49 | static int cb_rotate __P((SCR *)); | |
50 | ||
51 | /* | |
52 | * cut -- | |
53 | * Put a range of lines/columns into a buffer. | |
54 | * | |
55 | * There are two buffer areas, both found in the global structure. The first | |
56 | * is the linked list of all the buffers the user has named, the second is the | |
57 | * default buffer storage. There is a pointer, too, which is the current | |
58 | * default buffer, i.e. it may point to the default buffer or a named buffer | |
59 | * depending on into what buffer the last text was cut. In both delete and | |
60 | * yank operations, text is cut into either the buffer named by the user, or | |
61 | * the default buffer. If it's a delete of information on more than a single | |
62 | * line, the contents of the numbered buffers are rotated up one, the contents | |
63 | * of the buffer named '9' are discarded, and the text is also cut into the | |
64 | * buffer named '1'. | |
65 | * | |
66 | * In all cases, upper-case buffer names are the same as lower-case names, | |
67 | * with the exception that they cause the buffer to be appended to instead | |
68 | * of replaced. | |
69 | * | |
70 | * !!! | |
71 | * The contents of the default buffer would disappear after most operations in | |
72 | * historic vi. It's unclear that this is useful, so we don't bother. | |
73 | * | |
74 | * When users explicitly cut text into the numeric buffers, historic vi became | |
75 | * genuinely strange. I've never been able to figure out what was supposed to | |
76 | * happen. It behaved differently if you deleted text than if you yanked text, | |
77 | * and, in the latter case, the text was appended to the buffer instead of | |
78 | * replacing the contents. Hopefully it's not worth getting right. | |
79 | */ | |
80 | int | |
81 | cut(sp, ep, cbp, namep, fm, tm, flags) | |
82 | SCR *sp; | |
83 | EXF *ep; | |
84 | CB *cbp; | |
85 | CHAR_T *namep; | |
86 | int flags; | |
87 | MARK *fm, *tm; | |
88 | { | |
89 | CHAR_T name; | |
90 | TEXT *tp; | |
91 | recno_t lno; | |
92 | size_t len; | |
93 | int append, namedbuffer, setdefcb; | |
94 | ||
95 | #if defined(DEBUG) && 0 | |
96 | TRACE(sp, "cut: from {%lu, %d}, to {%lu, %d}%s\n", | |
97 | fm->lno, fm->cno, tm->lno, tm->cno, | |
98 | LF_ISSET(CUT_LINEMODE) ? " LINE MODE" : ""); | |
99 | #endif | |
100 | if (cbp == NULL) { | |
178c26c5 JH |
101 | if (LF_ISSET(CUT_DELETE) && |
102 | (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno)) { | |
1e64b3ba JH |
103 | (void)cb_rotate(sp); |
104 | name = '1'; | |
105 | goto defcb; | |
106 | } | |
107 | if (namep == NULL) { | |
108 | cbp = sp->gp->dcb_store; | |
109 | append = namedbuffer = 0; | |
110 | setdefcb = 1; | |
111 | } else { | |
112 | name = *namep; | |
113 | defcb: CBNAME(sp, cbp, name); | |
114 | append = isupper(name); | |
115 | namedbuffer = setdefcb = 1; | |
116 | } | |
117 | } else | |
118 | append = namedbuffer = setdefcb = 0; | |
119 | ||
120 | /* | |
121 | * If this is a new buffer, create it and add it into the list. | |
122 | * Otherwise, if it's not an append, free its current contents. | |
123 | */ | |
124 | if (cbp == NULL) { | |
125 | CALLOC(sp, cbp, CB *, 1, sizeof(CB)); | |
126 | cbp->name = name; | |
127 | CIRCLEQ_INIT(&cbp->textq); | |
128 | if (namedbuffer) { | |
129 | LIST_INSERT_HEAD(&sp->gp->cutq, cbp, q); | |
130 | } else | |
131 | sp->gp->dcb_store = cbp; | |
132 | } else if (!append) { | |
133 | text_lfree(&cbp->textq); | |
134 | cbp->len = 0; | |
135 | cbp->flags = 0; | |
136 | } | |
137 | ||
138 | /* In line mode, it's pretty easy, just cut the lines. */ | |
139 | if (LF_ISSET(CUT_LINEMODE)) { | |
140 | for (lno = fm->lno; lno <= tm->lno; ++lno) { | |
141 | if (cb_line(sp, ep, lno, 0, 0, &tp)) | |
142 | goto mem; | |
143 | CIRCLEQ_INSERT_TAIL(&cbp->textq, tp, q); | |
144 | cbp->len += tp->len; | |
145 | } | |
146 | cbp->flags |= CB_LMODE; | |
147 | } else { | |
148 | /* Get the first line. */ | |
149 | len = fm->lno < tm->lno ? 0 : tm->cno - fm->cno; | |
150 | if (cb_line(sp, ep, fm->lno, fm->cno, len, &tp)) | |
151 | goto mem; | |
152 | CIRCLEQ_INSERT_TAIL(&cbp->textq, tp, q); | |
153 | cbp->len += tp->len; | |
154 | ||
155 | /* Get the intermediate lines. */ | |
156 | for (lno = fm->lno; ++lno < tm->lno;) { | |
157 | if (cb_line(sp, ep, lno, 0, 0, &tp)) | |
158 | goto mem; | |
159 | CIRCLEQ_INSERT_TAIL(&cbp->textq, tp, q); | |
160 | cbp->len += tp->len; | |
161 | } | |
162 | ||
163 | /* Get the last line. */ | |
164 | if (tm->lno > fm->lno && tm->cno > 0) { | |
165 | if (cb_line(sp, ep, lno, 0, tm->cno, &tp)) { | |
166 | mem: if (append) | |
167 | msgq(sp, M_ERR, | |
168 | "Contents of %s buffer lost.", | |
169 | charname(sp, name)); | |
170 | text_lfree(&cbp->textq); | |
171 | cbp->len = 0; | |
172 | cbp->flags = 0; | |
173 | return (1); | |
174 | } | |
175 | CIRCLEQ_INSERT_TAIL(&cbp->textq, tp, q); | |
176 | cbp->len += tp->len; | |
177 | } | |
178 | } | |
179 | if (setdefcb) | |
180 | sp->gp->dcbp = cbp; /* Repoint default buffer. */ | |
181 | return (0); | |
182 | } | |
183 | ||
184 | /* | |
185 | * cb_rotate -- | |
186 | * Rotate the numbered buffers up one. | |
187 | */ | |
188 | static int | |
189 | cb_rotate(sp) | |
190 | SCR *sp; | |
191 | { | |
192 | CB *cbp, *del_cbp; | |
193 | ||
194 | del_cbp = NULL; | |
195 | for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) | |
196 | switch(cbp->name) { | |
197 | case '1': | |
198 | cbp->name = '2'; | |
199 | break; | |
200 | case '2': | |
201 | cbp->name = '3'; | |
202 | break; | |
203 | case '3': | |
204 | cbp->name = '4'; | |
205 | break; | |
206 | case '4': | |
207 | cbp->name = '5'; | |
208 | break; | |
209 | case '5': | |
210 | cbp->name = '6'; | |
211 | break; | |
212 | case '6': | |
213 | cbp->name = '7'; | |
214 | break; | |
215 | case '7': | |
216 | cbp->name = '8'; | |
217 | break; | |
218 | case '8': | |
219 | cbp->name = '9'; | |
220 | break; | |
221 | case '9': | |
222 | del_cbp = cbp; | |
223 | break; | |
224 | } | |
225 | if (del_cbp != NULL) { | |
226 | LIST_REMOVE(del_cbp, q); | |
227 | text_lfree(&del_cbp->textq); | |
228 | FREE(del_cbp, sizeof(CB)); | |
229 | } | |
230 | return (0); | |
231 | } | |
232 | ||
233 | /* | |
234 | * cb_line -- | |
235 | * Cut a portion of a single line. | |
236 | */ | |
237 | static int | |
238 | cb_line(sp, ep, lno, fcno, clen, newp) | |
239 | SCR *sp; | |
240 | EXF *ep; | |
241 | recno_t lno; | |
242 | size_t fcno, clen; | |
243 | TEXT **newp; | |
244 | { | |
245 | TEXT *tp; | |
246 | size_t len; | |
247 | char *p; | |
248 | ||
249 | if ((p = file_gline(sp, ep, lno, &len)) == NULL) { | |
250 | GETLINE_ERR(sp, lno); | |
251 | return (1); | |
252 | } | |
253 | ||
254 | if ((*newp = tp = text_init(sp, NULL, 0, len)) == NULL) | |
255 | return (1); | |
256 | ||
257 | /* | |
258 | * A length of zero means to cut from the MARK to the end | |
259 | * of the line. | |
260 | */ | |
261 | if (len != 0) { | |
262 | if (clen == 0) | |
263 | clen = len - fcno; | |
264 | memmove(tp->lb, p + fcno, clen); | |
265 | tp->len = clen; | |
266 | } | |
267 | return (0); | |
268 | } | |
269 | ||
270 | /* | |
271 | * text_init -- | |
272 | * Allocate a new TEXT structure. | |
273 | */ | |
274 | TEXT * | |
275 | text_init(sp, p, len, total_len) | |
276 | SCR *sp; | |
277 | const char *p; | |
278 | size_t len, total_len; | |
279 | { | |
280 | TEXT *tp; | |
281 | ||
282 | MALLOC(sp, tp, TEXT *, sizeof(TEXT)); | |
283 | if (tp == NULL) | |
284 | return (NULL); | |
285 | /* ANSI C doesn't define a call to malloc(2) for 0 bytes. */ | |
286 | if (tp->lb_len = total_len) { | |
287 | MALLOC(sp, tp->lb, CHAR_T *, tp->lb_len); | |
288 | if (tp->lb == NULL) { | |
289 | free(tp); | |
290 | return (NULL); | |
291 | } | |
292 | if (p != NULL && len != 0) | |
293 | memmove(tp->lb, p, len); | |
294 | } else | |
295 | tp->lb = NULL; | |
296 | tp->len = len; | |
297 | tp->ai = tp->insert = tp->offset = tp->owrite = 0; | |
298 | tp->wd = NULL; | |
299 | tp->wd_len = 0; | |
300 | return (tp); | |
301 | } | |
302 | ||
303 | /* | |
304 | * text_lfree -- | |
305 | * Free a chain of text structures. | |
306 | */ | |
307 | void | |
308 | text_lfree(headp) | |
309 | TEXTH *headp; | |
310 | { | |
311 | TEXT *tp; | |
312 | ||
313 | while ((tp = headp->cqh_first) != (void *)headp) { | |
314 | CIRCLEQ_REMOVE(headp, tp, q); | |
315 | text_free(tp); | |
316 | } | |
317 | } | |
318 | ||
319 | /* | |
320 | * text_free -- | |
321 | * Free a text structure. | |
322 | */ | |
323 | void | |
324 | text_free(tp) | |
325 | TEXT *tp; | |
326 | { | |
327 | if (tp->lb != NULL) | |
328 | FREE(tp->lb, tp->lb_len); | |
329 | if (tp->wd != NULL) | |
330 | FREE(tp->wd, tp->wd_len); | |
331 | FREE(tp, sizeof(TEXT)); | |
332 | } | |
333 | ||
334 | /* | |
335 | * put -- | |
336 | * Put text buffer contents into the file. | |
337 | * | |
338 | * !!! | |
339 | * Historically, pasting into a file with no lines in vi would preserve | |
340 | * the single blank line. This is almost certainly a result of the fact | |
341 | * that historic vi couldn't deal with a file that had no lines in it. | |
342 | * This implementation treats that as a bug, and does not retain the | |
343 | * blank line. | |
344 | */ | |
345 | int | |
346 | put(sp, ep, cbp, namep, cp, rp, append) | |
347 | SCR *sp; | |
348 | EXF *ep; | |
349 | CB *cbp; | |
350 | CHAR_T *namep; | |
351 | MARK *cp, *rp; | |
352 | int append; | |
353 | { | |
354 | CHAR_T name; | |
355 | TEXT *ltp, *tp; | |
356 | recno_t lno; | |
357 | size_t blen, clen, len; | |
358 | char *bp, *p, *t; | |
359 | ||
360 | if (cbp == NULL) | |
361 | if (namep == NULL) { | |
362 | cbp = sp->gp->dcbp; | |
363 | if (cbp == NULL) { | |
364 | msgq(sp, M_ERR, "The default buffer is empty."); | |
365 | return (1); | |
366 | } | |
367 | } else { | |
368 | name = *namep; | |
369 | CBNAME(sp, cbp, name); | |
370 | if (cbp == NULL) { | |
371 | msgq(sp, M_ERR, | |
372 | "Buffer %s is empty.", charname(sp, name)); | |
373 | return (1); | |
374 | } | |
375 | } | |
376 | tp = cbp->textq.cqh_first; | |
377 | ||
378 | /* | |
379 | * It's possible to do a put into an empty file, meaning that the | |
380 | * cut buffer simply becomes the file. It's a special case so | |
381 | * that we can ignore it in general. | |
382 | * | |
383 | * Historical practice is that the cursor ends up on the first | |
384 | * non-blank character of the first line inserted. | |
385 | */ | |
386 | if (cp->lno == 1) { | |
387 | if (file_lline(sp, ep, &lno)) | |
388 | return (1); | |
389 | if (lno == 0) { | |
390 | for (; tp != (void *)&cbp->textq; | |
391 | ++lno, tp = tp->q.cqe_next) | |
392 | if (file_aline(sp, ep, 1, lno, tp->lb, tp->len)) | |
393 | return (1); | |
394 | rp->lno = 1; | |
395 | rp->cno = 0; | |
396 | (void)nonblank(sp, ep, rp->lno, &rp->cno); | |
397 | goto ret; | |
398 | } | |
399 | } | |
400 | ||
401 | /* If a line mode buffer, append each new line into the file. */ | |
402 | if (F_ISSET(cbp, CB_LMODE)) { | |
403 | lno = append ? cp->lno : cp->lno - 1; | |
404 | rp->lno = lno + 1; | |
405 | for (; tp != (void *)&cbp->textq; ++lno, tp = tp->q.cqe_next) | |
406 | if (file_aline(sp, ep, 1, lno, tp->lb, tp->len)) | |
407 | return (1); | |
408 | rp->cno = 0; | |
409 | (void)nonblank(sp, ep, rp->lno, &rp->cno); | |
410 | goto ret; | |
411 | } | |
412 | ||
413 | /* | |
414 | * If buffer was cut in character mode, replace the current line with | |
415 | * one built from the portion of the first line to the left of the | |
416 | * split plus the first line in the CB. Append each intermediate line | |
417 | * in the CB. Append a line built from the portion of the first line | |
418 | * to the right of the split plus the last line in the CB. | |
419 | * | |
420 | * Get the first line. | |
421 | */ | |
422 | lno = cp->lno; | |
423 | if ((p = file_gline(sp, ep, lno, &len)) == NULL) { | |
424 | GETLINE_ERR(sp, lno); | |
425 | return (1); | |
426 | } | |
427 | ||
428 | GET_SPACE_RET(sp, bp, blen, tp->len + len + 1); | |
429 | t = bp; | |
430 | ||
431 | /* Original line, left of the split. */ | |
432 | if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { | |
433 | memmove(bp, p, clen); | |
434 | p += clen; | |
435 | t += clen; | |
436 | } | |
437 | ||
438 | /* First line from the CB. */ | |
439 | memmove(t, tp->lb, tp->len); | |
440 | t += tp->len; | |
441 | ||
442 | /* Calculate length left in original line. */ | |
443 | clen = len ? len - cp->cno - (append ? 1 : 0) : 0; | |
444 | ||
445 | /* | |
446 | * If no more lines in the CB, append the rest of the original | |
447 | * line and quit. Otherwise, build the last line before doing | |
448 | * the intermediate lines, because the line changes will lose | |
449 | * the cached line. | |
450 | */ | |
451 | if (tp->q.cqe_next == (void *)&cbp->textq) { | |
452 | /* | |
453 | * Historical practice is that if a non-line mode put | |
454 | * is inside a single line, the cursor ends up on the | |
455 | * last character inserted. | |
456 | */ | |
457 | rp->lno = lno; | |
458 | rp->cno = (t - bp) - 1; | |
459 | ||
460 | if (clen > 0) { | |
461 | memmove(t, p, clen); | |
462 | t += clen; | |
463 | } | |
464 | if (file_sline(sp, ep, lno, bp, t - bp)) | |
465 | goto mem; | |
466 | } else { | |
467 | /* | |
468 | * Have to build both the first and last lines of the | |
469 | * put before doing any sets or we'll lose the cached | |
470 | * line. Build both the first and last lines in the | |
471 | * same buffer, so we don't have to have another buffer | |
472 | * floating around. | |
473 | * | |
474 | * Last part of original line; check for space, reset | |
475 | * the pointer into the buffer. | |
476 | */ | |
477 | ltp = cbp->textq.cqh_last; | |
478 | len = t - bp; | |
479 | ADD_SPACE_RET(sp, bp, blen, ltp->len + clen); | |
480 | t = bp + len; | |
481 | ||
482 | /* Add in last part of the CB. */ | |
483 | memmove(t, ltp->lb, ltp->len); | |
484 | if (clen) | |
485 | memmove(t + ltp->len, p, clen); | |
486 | clen += ltp->len; | |
487 | ||
488 | /* | |
489 | * Now: bp points to the first character of the first | |
490 | * line, t points to the last character of the last | |
491 | * line, t - bp is the length of the first line, and | |
492 | * clen is the length of the last. Just figured you'd | |
493 | * want to know. | |
494 | * | |
495 | * Output the line replacing the original line. | |
496 | */ | |
497 | if (file_sline(sp, ep, lno, bp, t - bp)) | |
498 | goto mem; | |
499 | ||
500 | /* | |
501 | * Historical practice is that if a non-line mode put | |
502 | * covers multiple lines, the cursor ends up on the | |
503 | * first character inserted. (Of course.) | |
504 | */ | |
505 | rp->lno = lno; | |
506 | rp->cno = (t - bp) - 1; | |
507 | ||
508 | /* Output any intermediate lines in the CB. */ | |
509 | for (tp = tp->q.cqe_next; | |
510 | tp->q.cqe_next != (void *)&cbp->textq; | |
511 | ++lno, tp = tp->q.cqe_next) | |
512 | if (file_aline(sp, ep, 1, lno, tp->lb, tp->len)) | |
513 | goto mem; | |
514 | ||
515 | if (file_aline(sp, ep, 1, lno, t, clen)) { | |
516 | mem: FREE_SPACE(sp, bp, blen); | |
517 | return (1); | |
518 | } | |
519 | } | |
520 | FREE_SPACE(sp, bp, blen); | |
521 | ||
522 | /* Reporting... */ | |
523 | ret: sp->rptlines[L_PUT] += lno - cp->lno; | |
524 | ||
525 | return (0); | |
526 | } |