xref: /openbsd-src/usr.bin/mg/line.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: line.c,v 1.8 2001/05/24 03:05:23 mickey Exp $	*/
2 
3 /*
4  *		Text line handling.
5  *
6  * The functions in this file are a general set of line management
7  * utilities. They are the only routines that touch the text. They
8  * also touch the buffer and window structures to make sure that the
9  * necessary updating gets done.  There are routines in this file that
10  * handle the kill buffer too.  It isn't here for any good reason.
11  *
12  * Note that this code only updates the dot and mark values in the window
13  * list.  Since all the code acts on the current window, the buffer that
14  * we are editing must be displayed, which means that "b_nwnd" is non-zero,
15  * which means that the dot and mark values in the buffer headers are
16  * nonsense.
17  */
18 
19 #include "def.h"
20 
21 /*
22  * The number of bytes member from the start of the structure type should be
23  * computed at compile time.
24  */
25 
26 #ifndef OFFSET
27 #define OFFSET(type,member) ((char *)&(((type *)0)->member)-(char *)((type *)0))
28 #endif
29 
30 #ifndef NBLOCK
31 #define NBLOCK	16		/* Line block chunk size	 */
32 #endif
33 
34 #ifndef KBLOCK
35 #define KBLOCK	256		/* Kill buffer block size.	 */
36 #endif
37 
38 static char	*kbufp = NULL;	/* Kill buffer data.		 */
39 static RSIZE	 kused = 0;	/* # of bytes used in KB.	 */
40 static RSIZE	 ksize = 0;	/* # of bytes allocated in KB.	 */
41 static RSIZE	 kstart = 0;	/* # of first used byte in KB.	 */
42 
43 static int	 kgrow		__P((int));
44 
45 /*
46  * This routine allocates a block of memory large enough to hold a LINE
47  * containing "used" characters. The block is rounded up to whatever
48  * needs to be allocated. (use lallocx for lines likely to grow.)
49  * Return a pointer to the new block, or NULL if there isn't any memory
50  * left. Print a message in the message line if no space.
51  */
52 LINE *
53 lalloc(used)
54 	int used;
55 {
56 	LINE	*lp;
57 	int	 size;
58 
59 	/* any padding at the end of the structure is used */
60 	if ((size = used + OFFSET(LINE, l_text[0])) < sizeof(LINE))
61 	    size = sizeof(LINE);
62 #ifdef MALLOCROUND
63 	MALLOCROUND(size);	/* round up to a size optimal to malloc */
64 #endif
65 	if ((lp = malloc((unsigned)size)) == NULL) {
66 		ewprintf("Can't get %d bytes", size);
67 		return NULL;
68 	}
69 	lp->l_size = size - OFFSET(LINE, l_text[0]);
70 	lp->l_used = used;
71 	return lp;
72 }
73 
74 /*
75  * Like lalloc, only round amount desired up because this line will
76  * probably grow.  We always make room for at least one more char.
77  * (thus making 0 not a special case anymore.)
78  */
79 LINE *
80 lallocx(used)
81 	int used;
82 {
83 	int	 size;
84 	LINE	*lp;
85 
86 	size = (NBLOCK + used) & ~(NBLOCK - 1);
87 	if ((lp = lalloc(size)) != NULL)
88 		lp->l_used = used;
89 	return lp;
90 }
91 
92 /*
93  * Delete line "lp".  Fix all of the links that might point to it (they are
94  * moved to offset 0 of the next line.  Unlink the line from whatever buffer
95  * it might be in, and release the memory.  The buffers are updated too; the
96  * magic conditions described in the above comments don't hold here.
97  */
98 void
99 lfree(lp)
100 	LINE *lp;
101 {
102 	BUFFER	*bp;
103 	MGWIN	*wp;
104 
105 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
106 		if (wp->w_linep == lp)
107 			wp->w_linep = lp->l_fp;
108 		if (wp->w_dotp == lp) {
109 			wp->w_dotp = lp->l_fp;
110 			wp->w_doto = 0;
111 		}
112 		if (wp->w_markp == lp) {
113 			wp->w_markp = lp->l_fp;
114 			wp->w_marko = 0;
115 		}
116 	}
117 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
118 		if (bp->b_nwnd == 0) {
119 			if (bp->b_dotp == lp) {
120 				bp->b_dotp = lp->l_fp;
121 				bp->b_doto = 0;
122 			}
123 			if (bp->b_markp == lp) {
124 				bp->b_markp = lp->l_fp;
125 				bp->b_marko = 0;
126 			}
127 		}
128 	}
129 	lp->l_bp->l_fp = lp->l_fp;
130 	lp->l_fp->l_bp = lp->l_bp;
131 	free((char *)lp);
132 }
133 
134 /*
135  * This routine is called when a character changes in place in the current
136  * buffer. It updates all of the required flags in the buffer and window
137  * system. The flag used is passed as an argument; if the buffer is being
138  * displayed in more than 1 window we change EDIT to HARD. Set MODE if the
139  * mode line needs to be updated (the "*" has to be set).
140  */
141 void
142 lchange(flag)
143 	int flag;
144 {
145 	MGWIN	*wp;
146 
147 	/* update mode lines if this is the first change. */
148 	if ((curbp->b_flag & BFCHG) == 0) {
149 		flag |= WFMODE;
150 		curbp->b_flag |= BFCHG;
151 	}
152 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
153 		if (wp->w_bufp == curbp) {
154 			wp->w_flag |= flag;
155 			if (wp != curwp)
156 				wp->w_flag |= WFHARD;
157 		}
158 	}
159 }
160 
161 /*
162  * Insert "n" copies of the character "c" at the current location of dot.
163  * In the easy case all that happens is the text is stored in the line.
164  * In the hard case, the line has to be reallocated.  When the window list
165  * is updated, take special care; I screwed it up once.  You always update
166  * dot in the current window.  You update mark and a dot in another window
167  * if it is greater than the place where you did the insert. Return TRUE
168  * if all is well, and FALSE on errors.
169  */
170 int
171 linsert(n, c)
172 	int n, c;
173 {
174 	LINE	*lp1, *lp2, *lp3;
175 	MGWIN	*wp;
176 	RSIZE	 i;
177 	int	 doto;
178 	char	*cp1, *cp2;
179 
180 	lchange(WFEDIT);
181 
182 	/* current line */
183 	lp1 = curwp->w_dotp;
184 
185 	/* special case for the end */
186 	if (lp1 == curbp->b_linep) {
187 		/* now should only happen in empty buffer */
188 		if (curwp->w_doto != 0) {
189 			ewprintf("bug: linsert");
190 			return FALSE;
191 		}
192 		/* allocate a new line */
193 		if ((lp2 = lallocx(n)) == NULL)
194 			return FALSE;
195 		/* previous line */
196 		lp3 = lp1->l_bp;
197 		/* link in */
198 		lp3->l_fp = lp2;
199 		lp2->l_fp = lp1;
200 		lp1->l_bp = lp2;
201 		lp2->l_bp = lp3;
202 		for (i = 0; i < n; ++i)
203 			lp2->l_text[i] = c;
204 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
205 			if (wp->w_linep == lp1)
206 				wp->w_linep = lp2;
207 			if (wp->w_dotp == lp1)
208 				wp->w_dotp = lp2;
209 			if (wp->w_markp == lp1)
210 				wp->w_markp = lp2;
211 		}
212 		/* NOSTRICT */
213 		curwp->w_doto = n;
214 		return TRUE;
215 	}
216 	/* save for later */
217 	doto = curwp->w_doto;
218 	/* NOSTRICT (2) */
219 	/* Hard case: reallocate */
220 	if (lp1->l_used + n > lp1->l_size) {
221 		if ((lp2 = lallocx(lp1->l_used + n)) == NULL)
222 			return FALSE;
223 		cp1 = &lp1->l_text[0];
224 		cp2 = &lp2->l_text[0];
225 		while (cp1 != &lp1->l_text[doto])
226 			*cp2++ = *cp1++;
227 		/* NOSTRICT */
228 		cp2 += n;
229 		while (cp1 != &lp1->l_text[lp1->l_used])
230 			*cp2++ = *cp1++;
231 		lp1->l_bp->l_fp = lp2;
232 		lp2->l_fp = lp1->l_fp;
233 		lp1->l_fp->l_bp = lp2;
234 		lp2->l_bp = lp1->l_bp;
235 		free((char *)lp1);
236 	/* Easy case: in place */
237 	} else {
238 		/* pretend there's a new line */
239 		lp2 = lp1;
240 		/* NOSTRICT */
241 		lp2->l_used += n;
242 		cp2 = &lp1->l_text[lp1->l_used];
243 
244 		cp1 = cp2 - n;
245 		while (cp1 != &lp1->l_text[doto])
246 			*--cp2 = *--cp1;
247 	}
248 	/* Add the characters */
249 	for (i = 0; i < n; ++i)
250 		lp2->l_text[doto + i] = c;
251 
252 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
253 		if (wp->w_linep == lp1)
254 			wp->w_linep = lp2;
255 		if (wp->w_dotp == lp1) {
256 			wp->w_dotp = lp2;
257 			if (wp == curwp || wp->w_doto > doto)
258 				/* NOSTRICT */
259 				wp->w_doto += n;
260 		}
261 		if (wp->w_markp == lp1) {
262 			wp->w_markp = lp2;
263 			if (wp->w_marko > doto)
264 				/* NOSTRICT */
265 				wp->w_marko += n;
266 		}
267 	}
268 	return TRUE;
269 }
270 
271 /*
272  * Insert a newline into the buffer at the current location of dot in the
273  * current window.  The funny ass-backwards way is no longer used.
274  */
275 int
276 lnewline()
277 {
278 	LINE	*lp1, *lp2;
279 	int	 doto, nlen;
280 	MGWIN	*wp;
281 
282 	lchange(WFHARD);
283 
284 	/* Get the address and offset of "." */
285 	lp1 = curwp->w_dotp;
286 	doto = curwp->w_doto;
287 
288 	/* avoid unnecessary copying */
289 	if (doto == 0) {
290 		/* new first part */
291 		if ((lp2 = lallocx(0)) == NULL)
292 			return FALSE;
293 		lp2->l_bp = lp1->l_bp;
294 		lp1->l_bp->l_fp = lp2;
295 		lp2->l_fp = lp1;
296 		lp1->l_bp = lp2;
297 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
298 			if (wp->w_linep == lp1)
299 				wp->w_linep = lp2;
300 		return TRUE;
301 	}
302 
303 	/* length of new part */
304 	nlen = llength(lp1) - doto;
305 
306 	/* new second half line */
307 	if ((lp2 = lallocx(nlen)) == NULL)
308 		return FALSE;
309 	if (nlen != 0)
310 		bcopy(&lp1->l_text[doto], &lp2->l_text[0], nlen);
311 	lp1->l_used = doto;
312 	lp2->l_bp = lp1;
313 	lp2->l_fp = lp1->l_fp;
314 	lp1->l_fp = lp2;
315 	lp2->l_fp->l_bp = lp2;
316 	/* Windows */
317 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
318 		if (wp->w_dotp == lp1 && wp->w_doto >= doto) {
319 			wp->w_dotp = lp2;
320 			wp->w_doto -= doto;
321 		}
322 		if (wp->w_markp == lp1 && wp->w_marko >= doto) {
323 			wp->w_markp = lp2;
324 			wp->w_marko -= doto;
325 		}
326 	}
327 	return TRUE;
328 }
329 
330 /*
331  * This function deletes "n" bytes, starting at dot. It understands how to
332  * deal with end of lines, etc.  It returns TRUE if all of the characters
333  * were deleted, and FALSE if they were not (because dot ran into the end
334  * of the buffer.  The "kflag" indicates either no insertion, or direction
335  * of insertion into the kill buffer.
336  */
337 int
338 ldelete(n, kflag)
339 	RSIZE n;
340 	int   kflag;
341 {
342 	LINE	*dotp;
343 	RSIZE	 chunk;
344 	MGWIN	*wp;
345 	int	 doto;
346 	char	*cp1, *cp2;
347 
348 	/*
349 	 * HACK - doesn't matter, and fixes back-over-nl bug for empty
350 	 *	kill buffers.
351 	 */
352 	if (kused == kstart)
353 		kflag = KFORW;
354 
355 	while (n != 0) {
356 		dotp = curwp->w_dotp;
357 		doto = curwp->w_doto;
358 		/* Hit the end of the buffer */
359 		if (dotp == curbp->b_linep)
360 			return FALSE;
361 		/* Size of the chunk */
362 		chunk = dotp->l_used - doto;
363 		if (chunk > n)
364 			chunk = n;
365 		/* End of line, merge */
366 		if (chunk == 0) {
367 			if (dotp == lback(curbp->b_linep))
368 				/* End of buffer */
369 				return FALSE;
370 			lchange(WFHARD);
371 			if (ldelnewline() == FALSE ||
372 			    (kflag != KNONE && kinsert('\n', kflag) == FALSE))
373 				return FALSE;
374 			--n;
375 			continue;
376 		}
377 		lchange(WFEDIT);
378 		/* Scrunch text */
379 		cp1 = &dotp->l_text[doto];
380 		cp2 = cp1 + chunk;
381 		if (kflag == KFORW) {
382 			while (ksize - kused < chunk)
383 				if (kgrow(FALSE) == FALSE)
384 					return FALSE;
385 			bcopy(cp1, &(kbufp[kused]), (int)chunk);
386 			kused += chunk;
387 		} else if (kflag == KBACK) {
388 			while (kstart < chunk)
389 				if (kgrow(TRUE) == FALSE)
390 					return FALSE;
391 			bcopy(cp1, &(kbufp[kstart - chunk]), (int)chunk);
392 			kstart -= chunk;
393 		} else if (kflag != KNONE)
394 			panic("broken ldelete call");
395 		while (cp2 != &dotp->l_text[dotp->l_used])
396 			*cp1++ = *cp2++;
397 		dotp->l_used -= (int)chunk;
398 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
399 			if (wp->w_dotp == dotp && wp->w_doto >= doto) {
400 				/* NOSTRICT */
401 				wp->w_doto -= chunk;
402 				if (wp->w_doto < doto)
403 					wp->w_doto = doto;
404 			}
405 			if (wp->w_markp == dotp && wp->w_marko >= doto) {
406 				/* NOSTRICT */
407 				wp->w_marko -= chunk;
408 				if (wp->w_marko < doto)
409 					wp->w_marko = doto;
410 			}
411 		}
412 		n -= chunk;
413 	}
414 	return TRUE;
415 }
416 
417 /*
418  * Delete a newline and join the current line with the next line. If the next
419  * line is the magic header line always return TRUE; merging the last line
420  * with the header line can be thought of as always being a successful
421  * operation.  Even if nothing is done, this makes the kill buffer work
422  * "right".  Easy cases can be done by shuffling data around.  Hard cases
423  * require that lines be moved about in memory.  Return FALSE on error and
424  * TRUE if all looks ok.
425  */
426 int
427 ldelnewline()
428 {
429 	LINE	*lp1, *lp2, *lp3;
430 	MGWIN	*wp;
431 
432 	lp1 = curwp->w_dotp;
433 	lp2 = lp1->l_fp;
434 	/* at the end of the buffer */
435 	if (lp2 == curbp->b_linep)
436 		return TRUE;
437 	if (lp2->l_used <= lp1->l_size - lp1->l_used) {
438 		bcopy(&lp2->l_text[0], &lp1->l_text[lp1->l_used], lp2->l_used);
439 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
440 			if (wp->w_linep == lp2)
441 				wp->w_linep = lp1;
442 			if (wp->w_dotp == lp2) {
443 				wp->w_dotp = lp1;
444 				wp->w_doto += lp1->l_used;
445 			}
446 			if (wp->w_markp == lp2) {
447 				wp->w_markp = lp1;
448 				wp->w_marko += lp1->l_used;
449 			}
450 		}
451 		lp1->l_used += lp2->l_used;
452 		lp1->l_fp = lp2->l_fp;
453 		lp2->l_fp->l_bp = lp1;
454 		free((char *)lp2);
455 		return TRUE;
456 	}
457 	if ((lp3 = lalloc(lp1->l_used + lp2->l_used)) == NULL)
458 		return FALSE;
459 	bcopy(&lp1->l_text[0], &lp3->l_text[0], lp1->l_used);
460 	bcopy(&lp2->l_text[0], &lp3->l_text[lp1->l_used], lp2->l_used);
461 	lp1->l_bp->l_fp = lp3;
462 	lp3->l_fp = lp2->l_fp;
463 	lp2->l_fp->l_bp = lp3;
464 	lp3->l_bp = lp1->l_bp;
465 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
466 		if (wp->w_linep == lp1 || wp->w_linep == lp2)
467 			wp->w_linep = lp3;
468 		if (wp->w_dotp == lp1)
469 			wp->w_dotp = lp3;
470 		else if (wp->w_dotp == lp2) {
471 			wp->w_dotp = lp3;
472 			wp->w_doto += lp1->l_used;
473 		}
474 		if (wp->w_markp == lp1)
475 			wp->w_markp = lp3;
476 		else if (wp->w_markp == lp2) {
477 			wp->w_markp = lp3;
478 			wp->w_marko += lp1->l_used;
479 		}
480 	}
481 	free((char *)lp1);
482 	free((char *)lp2);
483 	return TRUE;
484 }
485 
486 /*
487  * Replace plen characters before dot with argument string.  Control-J
488  * characters in st are interpreted as newlines.  There is a casehack
489  * disable flag (normally it likes to match case of replacement to what
490  * was there).
491  */
492 int
493 lreplace(plen, st, f)
494 	RSIZE	plen;	/* length to remove		 */
495 	char	*st;	/* replacement string		 */
496 	int	f;	/* case hack disable		 */
497 {
498 	RSIZE	rlen;	/* replacement length		 */
499 	int	rtype;	/* capitalization		 */
500 	int	c;	/* used for random characters	 */
501 	int	doto;	/* offset into line		 */
502 
503 	/*
504 	 * Find the capitalization of the word that was found.  f says use
505 	 * exact case of replacement string (same thing that happens with
506 	 * lowercase found), so bypass check.
507 	 */
508 	/* NOSTRICT */
509 	(void)backchar(FFARG | FFRAND, (int)plen);
510 	rtype = _L;
511 	c = lgetc(curwp->w_dotp, curwp->w_doto);
512 	if (ISUPPER(c) != FALSE && f == FALSE) {
513 		rtype = _U | _L;
514 		if (curwp->w_doto + 1 < llength(curwp->w_dotp)) {
515 			c = lgetc(curwp->w_dotp, curwp->w_doto + 1);
516 			if (ISUPPER(c) != FALSE) {
517 				rtype = _U;
518 			}
519 		}
520 	}
521 	/*
522 	 * make the string lengths match (either pad the line
523 	 * so that it will fit, or scrunch out the excess).
524 	 * be careful with dot's offset.
525 	 */
526 	rlen = strlen(st);
527 	doto = curwp->w_doto;
528 	if (plen > rlen)
529 		(void)ldelete((RSIZE) (plen - rlen), KNONE);
530 	else if (plen < rlen) {
531 		if (linsert((int)(rlen - plen), ' ') == FALSE)
532 			return FALSE;
533 	}
534 	curwp->w_doto = doto;
535 
536 	/*
537 	 * do the replacement:	If was capital, then place first
538 	 * char as if upper, and subsequent chars as if lower.
539 	 * If inserting upper, check replacement for case.
540 	 */
541 	while ((c = CHARMASK(*st++)) != '\0') {
542 		if ((rtype & _U) != 0 && ISLOWER(c) != 0)
543 			c = TOUPPER(c);
544 		if (rtype == (_U | _L))
545 			rtype = _L;
546 		if (c == CCHR('J')) {
547 			if (curwp->w_doto == llength(curwp->w_dotp))
548 				(void)forwchar(FFRAND, 1);
549 			else {
550 				if (ldelete((RSIZE) 1, KNONE) != FALSE)
551 					(void)lnewline();
552 			}
553 		} else if (curwp->w_dotp == curbp->b_linep) {
554 			(void)linsert(1, c);
555 		} else if (curwp->w_doto == llength(curwp->w_dotp)) {
556 			if (ldelete((RSIZE) 1, KNONE) != FALSE)
557 				(void)linsert(1, c);
558 		} else
559 			lputc(curwp->w_dotp, curwp->w_doto++, c);
560 	}
561 	lchange(WFHARD);
562 	return (TRUE);
563 }
564 
565 /*
566  * Delete all of the text saved in the kill buffer.  Called by commands when
567  * a new kill context is created. The kill buffer array is released, just in
568  * case the buffer has grown to an immense size.  No errors.
569  */
570 void
571 kdelete()
572 {
573 	if (kbufp != NULL) {
574 		free((char *)kbufp);
575 		kbufp = NULL;
576 		kstart = kused = ksize = 0;
577 	}
578 }
579 
580 /*
581  * Insert a character to the kill buffer, enlarging the buffer if there
582  * isn't any room. Always grow the buffer in chunks, on the assumption
583  * that if you put something in the kill buffer you are going to put more
584  * stuff there too later. Return TRUE if all is well, and FALSE on errors.
585  * Print a message on errors.  Dir says whether to put it at back or front.
586  */
587 int
588 kinsert(c, dir)
589 	int c, dir;
590 {
591 	if (kused == ksize && dir == KFORW && kgrow(FALSE) == FALSE)
592 		return FALSE;
593 	if (kstart == 0 && dir == KBACK && kgrow(TRUE) == FALSE)
594 		return FALSE;
595 	if (dir == KFORW)
596 		kbufp[kused++] = c;
597 	else if (dir == KBACK)
598 		kbufp[--kstart] = c;
599 	else
600 		panic("broken kinsert call");	/* Oh shit! */
601 	return (TRUE);
602 }
603 
604 /*
605  * kgrow - just get more kill buffer for the callee. back is true if
606  * we are trying to get space at the beginning of the kill buffer.
607  */
608 static int
609 kgrow(back)
610 	int back;
611 {
612 	int	 nstart;
613 	char	*nbufp;
614 
615 	if ((unsigned)(ksize + KBLOCK) <= (unsigned)ksize) {
616 		/* probably 16 bit unsigned */
617 		ewprintf("Kill buffer size at maximum");
618 		return FALSE;
619 	}
620 	if ((nbufp = malloc((unsigned)(ksize + KBLOCK))) == NULL) {
621 		ewprintf("Can't get %ld bytes", (long)(ksize + KBLOCK));
622 		return FALSE;
623 	}
624 	nstart = (back == TRUE) ? (kstart + KBLOCK) : (KBLOCK / 4);
625 	bcopy(&(kbufp[kstart]), &(nbufp[nstart]), (int)(kused - kstart));
626 	if (kbufp != NULL)
627 		free((char *)kbufp);
628 	kbufp = nbufp;
629 	ksize += KBLOCK;
630 	kused = kused - kstart + nstart;
631 	kstart = nstart;
632 	return TRUE;
633 }
634 
635 /*
636  * This function gets characters from the kill buffer. If the character
637  * index "n" is off the end, it returns "-1". This lets the caller just
638  * scan along until it gets a "-1" back.
639  */
640 int
641 kremove(n)
642 	int n;
643 {
644 	if (n < 0 || n + kstart >= kused)
645 		return -1;
646 	return CHARMASK(kbufp[n + kstart]);
647 }
648