xref: /openbsd-src/usr.bin/mg/buffer.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: buffer.c,v 1.31 2003/06/26 23:04:10 vincent Exp $	*/
2 
3 /*
4  *		Buffer handling.
5  */
6 
7 #include "def.h"
8 #include "kbd.h"		/* needed for modes */
9 #include <stdarg.h>
10 
11 static BUFFER  *makelist(void);
12 
13 int
14 togglereadonly(int f, int n)
15 {
16 	if (!(curbp->b_flag & BFREADONLY)) {
17 		curbp->b_flag |= BFREADONLY;
18 		ewprintf("Now readonly");
19 	} else {
20 		curbp->b_flag &=~ BFREADONLY;
21 		if (curbp->b_flag & BFCHG)
22 			ewprintf("Warning: Buffer was modified");
23 	}
24 	curwp->w_flag |= WFMODE;
25 
26 	return(1);
27 }
28 
29 /*
30  * Attach a buffer to a window. The values of dot and mark come
31  * from the buffer if the use count is 0. Otherwise, they come
32  * from some other window.  *scratch* is the default alternate
33  * buffer.
34  */
35 /* ARGSUSED */
36 int
37 usebuffer(int f, int n)
38 {
39 	BUFFER *bp;
40 	int     s;
41 	char    bufn[NBUFN];
42 
43 	/* Get buffer to use from user */
44 	if ((curbp->b_altb == NULL) &&
45 	    ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL))
46 		s = eread("Switch to buffer: ", bufn, NBUFN, EFNEW | EFBUF);
47 	else
48 		s = eread("Switch to buffer: (default %s) ", bufn, NBUFN,
49 			  EFNEW | EFBUF, curbp->b_altb->b_bname);
50 
51 	if (s == ABORT)
52 		return s;
53 	if (s == FALSE && curbp->b_altb != NULL)
54 		bp = curbp->b_altb;
55 	else if ((bp = bfind(bufn, TRUE)) == NULL)
56 		return FALSE;
57 
58 	/* and put it in current window */
59 	curbp = bp;
60 	return showbuffer(bp, curwp, WFFORCE | WFHARD);
61 }
62 
63 /*
64  * pop to buffer asked for by the user.
65  */
66 /* ARGSUSED */
67 int
68 poptobuffer(int f, int n)
69 {
70 	BUFFER *bp;
71 	MGWIN  *wp;
72 	int     s;
73 	char    bufn[NBUFN];
74 
75 	/* Get buffer to use from user */
76 	if ((curbp->b_altb == NULL) &&
77 	    ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL))
78 		s = eread("Switch to buffer in other window: ", bufn, NBUFN,
79 			  EFNEW | EFBUF);
80 	else
81 		s = eread("Switch to buffer in other window: (default %s) ",
82 			bufn, NBUFN, EFNEW | EFBUF, curbp->b_altb->b_bname);
83 	if (s == ABORT)
84 		return s;
85 	if (s == FALSE && curbp->b_altb != NULL)
86 		bp = curbp->b_altb;
87 	else if ((bp = bfind(bufn, TRUE)) == NULL)
88 		return FALSE;
89 
90 	/* and put it in a new window */
91 	if ((wp = popbuf(bp)) == NULL)
92 		return FALSE;
93 	curbp = bp;
94 	curwp = wp;
95 	return TRUE;
96 }
97 
98 /*
99  * Dispose of a buffer, by name.
100  * Ask for the name. Look it up (don't get too
101  * upset if it isn't there at all!). Clear the buffer (ask
102  * if the buffer has been changed). Then free the header
103  * line and the buffer header. Bound to "C-X K".
104  */
105 /* ARGSUSED */
106 int
107 killbuffer(int f, int n)
108 {
109 	BUFFER *bp;
110 	BUFFER *bp1;
111 	BUFFER *bp2;
112 	MGWIN  *wp;
113 	int     s;
114 	char    bufn[NBUFN];
115 	struct undo_rec *rec, *next;
116 
117 	if ((s = eread("Kill buffer: (default %s) ", bufn, NBUFN, EFNEW | EFBUF,
118 	    curbp->b_bname)) == ABORT)
119 		return (s);
120 	else if (s == FALSE)
121 		bp = curbp;
122 	else if ((bp = bfind(bufn, FALSE)) == NULL)
123 		return FALSE;
124 
125 	/*
126 	 * Find some other buffer to display. try the alternate buffer,
127 	 * then the first different buffer in the buffer list.  If there's
128 	 * only one buffer, create buffer *scratch* and make it the alternate
129 	 * buffer.  Return if *scratch* is only buffer...
130 	 */
131 	if ((bp1 = bp->b_altb) == NULL) {
132 		bp1 = (bp == bheadp) ? bp->b_bufp : bheadp;
133 		if (bp1 == NULL) {
134 			/* only one buffer. see if it's *scratch* */
135 			if (bp == bfind("*scratch*", FALSE))
136 				return FALSE;
137 			/* create *scratch* for alternate buffer */
138 			if ((bp1 = bfind("*scratch*", TRUE)) == NULL)
139 				return FALSE;
140 		}
141 	}
142 	if (bclear(bp) != TRUE)
143 		return TRUE;
144 	for (wp = wheadp; bp->b_nwnd > 0; wp = wp->w_wndp) {
145 		if (wp->w_bufp == bp) {
146 			bp2 = bp1->b_altb;	/* save alternate buffer */
147 			if (showbuffer(bp1, wp, WFMODE | WFFORCE | WFHARD))
148 				bp1->b_altb = bp2;
149 			else
150 				bp1 = bp2;
151 		}
152 	}
153 	if (bp == curbp)
154 		curbp = bp1;
155 	free(bp->b_linep);			/* Release header line.  */
156 	bp2 = NULL;				/* Find the header.	 */
157 	bp1 = bheadp;
158 	while (bp1 != bp) {
159 		if (bp1->b_altb == bp)
160 			bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
161 		bp2 = bp1;
162 		bp1 = bp1->b_bufp;
163 	}
164 	bp1 = bp1->b_bufp;			/* Next one in chain.	 */
165 	if (bp2 == NULL)			/* Unlink it.		 */
166 		bheadp = bp1;
167 	else
168 		bp2->b_bufp = bp1;
169 	while (bp1 != NULL) {			/* Finish with altb's	 */
170 		if (bp1->b_altb == bp)
171 			bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
172 		bp1 = bp1->b_bufp;
173 	}
174 	rec = LIST_FIRST(&bp->b_undo);
175 	while (rec != NULL) {
176 		next = LIST_NEXT(rec, next);
177 		free_undo_record(rec);
178 		rec = next;
179 	}
180 	free((char *)bp->b_bname);		/* Release name block	 */
181 	free(bp);				/* Release buffer block */
182 	return TRUE;
183 }
184 
185 /*
186  * Save some buffers - just call anycb with the arg flag.
187  */
188 /* ARGSUSED */
189 int
190 savebuffers(int f, int n)
191 {
192 	if (anycb(f) == ABORT)
193 		return ABORT;
194 	return TRUE;
195 }
196 
197 /*
198  * Listing buffers.
199  */
200 static int listbuf_ncol;
201 
202 static int	listbuf_goto_buffer(int f, int n);
203 
204 static PF listbuf_pf[] = {
205 	listbuf_goto_buffer,
206 };
207 
208 static struct KEYMAPE (1 + IMAPEXT) listbufmap = {
209 	1,
210 	1 + IMAPEXT,
211 	rescan,
212 	{
213 		{ CCHR('M'), CCHR('M'), listbuf_pf, NULL },
214 	}
215 };
216 
217 /*
218  * Display the buffer list. This is done
219  * in two parts. The "makelist" routine figures out
220  * the text, and puts it in a buffer. "popbuf"
221  * then pops the data onto the screen. Bound to
222  * "C-X C-B".
223  */
224 /* ARGSUSED */
225 int
226 listbuffers(int f, int n)
227 {
228 	static int initialized = 0;
229 	BUFFER *bp;
230 	MGWIN  *wp;
231 
232 	if (!initialized) {
233 		maps_add((KEYMAP *)&listbufmap, "listbufmap");
234 		initialized = 1;
235 	}
236 
237 	if ((bp = makelist()) == NULL || (wp = popbuf(bp)) == NULL)
238 		return FALSE;
239 	wp->w_dotp = bp->b_dotp;/* fix up if window already on screen */
240 	wp->w_doto = bp->b_doto;
241 	bp->b_modes[0] = name_mode("fundamental");
242 	bp->b_modes[1] = name_mode("listbufmap");
243 	bp->b_nmodes = 1;
244 
245 	return TRUE;
246 }
247 
248 /*
249  * This routine rebuilds the text for the
250  * list buffers command. Return TRUE if
251  * everything works. Return FALSE if there
252  * is an error (if there is no memory).
253  */
254 static BUFFER *
255 makelist(void)
256 {
257 	int	w = ncol / 2;
258 	BUFFER *bp, *blp;
259 	LINE   *lp;
260 
261 
262 	if ((blp = bfind("*Buffer List*", TRUE)) == NULL)
263 		return NULL;
264 	if (bclear(blp) != TRUE)
265 		return NULL;
266 	blp->b_flag &= ~BFCHG;		/* Blow away old.	 */
267 	blp->b_flag |= BFREADONLY;
268 
269 	listbuf_ncol = ncol;		/* cache ncol for listbuf_goto_buffer */
270 
271 	if (addlinef(blp, "%-*s%s", w, " MR Buffer", "Size   File") == FALSE ||
272 	    addlinef(blp, "%-*s%s", w, " -- ------", "----   ----") == FALSE)
273 		return NULL;
274 
275 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
276 		RSIZE nbytes;
277 
278 		nbytes = 0;			/* Count bytes in buf.	 */
279 		if (bp != blp) {
280 			lp = lforw(bp->b_linep);
281 			while (lp != bp->b_linep) {
282 				nbytes += llength(lp) + 1;
283 				lp = lforw(lp);
284 			}
285 			if (nbytes)
286 				nbytes--;	/* no bonus newline	 */
287 		}
288 
289 		if (addlinef(blp, "%c%c%c %-*.*s%c%-6d %-*s",
290 		    (bp == curbp) ? '.' : ' ',	/* current buffer ? */
291 		    ((bp->b_flag & BFCHG) != 0) ? '*' : ' ',	/* changed ? */
292 		    ((bp->b_flag & BFREADONLY) != 0) ? ' ' : '*',
293 		    w - 5,		/* four chars already written */
294 		    w - 5,		/* four chars already written */
295 		    bp->b_bname,	/* buffer name */
296 		    strlen(bp->b_bname) < w - 5 ? ' ' : '$', /* truncated? */
297 		    nbytes,		/* buffer size */
298 		    w - 7,		/* seven chars already written */
299 		    bp->b_fname) == FALSE)
300 			return NULL;
301 	}
302 	blp->b_dotp = lforw(blp->b_linep);	/* put dot at beginning of
303 						 * buffer */
304 	blp->b_doto = 0;
305 	return blp;				/* All done		 */
306 }
307 
308 static int
309 listbuf_goto_buffer(int f, int n)
310 {
311 	BUFFER *bp;
312 	MGWIN *wp;
313 	char *line;
314 	int i;
315 
316 	if (curwp->w_dotp->l_text[listbuf_ncol/2 - 1] == '$') {
317 		ewprintf("buffer name truncated");
318 		return FALSE;
319 	}
320 
321 	if ((line = malloc(listbuf_ncol/2)) == NULL)
322 		return FALSE;
323 
324 	memcpy(line, curwp->w_dotp->l_text + 4, listbuf_ncol/2 - 5);
325 	for (i = listbuf_ncol/2 - 6; i > 0; i--) {
326 		if (line[i] != ' ') {
327 			line[i + 1] = '\0';
328 			break;
329 		}
330 	}
331 	if (i == 0) {
332 		return FALSE;
333 	}
334 
335 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
336 		if (strcmp(bp->b_bname, line) == 0)
337 			break;
338 	}
339 	if (bp == NULL) {
340 		return FALSE;
341 	}
342 	if ((wp = popbuf(bp)) == NULL)
343 		return FALSE;
344 	curbp = bp;
345 	curwp = wp;
346 
347 	return TRUE;
348 }
349 
350 /*
351  * The argument "text" points to a format string.  Append this line to the
352  * buffer. Handcraft the EOL on the end.  Return TRUE if it worked and
353  * FALSE if you ran out of room.
354  */
355 int
356 addlinef(BUFFER *bp, char *fmt, ...)
357 {
358 	va_list ap;
359 	LINE  *lp;
360 
361 	if ((lp = lalloc(0)) == NULL)
362 		return (FALSE);
363 	va_start(ap, fmt);
364 	if (vasprintf(&lp->l_text, fmt, ap) == -1) {
365 		lfree(lp);
366 		va_end(ap);
367 		return (FALSE);
368 	}
369 	lp->l_used = strlen(lp->l_text);
370 	va_end(ap);
371 
372 	bp->b_linep->l_bp->l_fp = lp;		/* Hook onto the end	 */
373 	lp->l_bp = bp->b_linep->l_bp;
374 	bp->b_linep->l_bp = lp;
375 	lp->l_fp = bp->b_linep;
376 
377 	return TRUE;
378 }
379 
380 /*
381  * Look through the list of buffers, giving the user a chance to save them.
382  * Return TRUE if there are any changed buffers afterwards.  Buffers that
383  * don't have an associated file don't count.  Return FALSE if there are
384  * no changed buffers.
385  */
386 int
387 anycb(int f)
388 {
389 	BUFFER *bp;
390 	int     s = FALSE, save = FALSE;
391 	char    prompt[NFILEN + 11];
392 
393 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
394 		if (bp->b_fname != NULL && *(bp->b_fname) != '\0' &&
395 		    (bp->b_flag & BFCHG) != 0) {
396 			snprintf(prompt, sizeof prompt, "Save file %s",
397 			    bp->b_fname);
398 			if ((f == TRUE || (save = eyorn(prompt)) == TRUE) &&
399 			    buffsave(bp) == TRUE) {
400 				bp->b_flag &= ~BFCHG;
401 				upmodes(bp);
402 			} else
403 				s = TRUE;
404 			if (save == ABORT)
405 				return (save);
406 			save = TRUE;
407 		}
408 	}
409 	if (save == FALSE /* && kbdmop == NULL */ )	/* experimental */
410 		ewprintf("(No files need saving)");
411 	return s;
412 }
413 
414 /*
415  * Search for a buffer, by name.
416  * If not found, and the "cflag" is TRUE,
417  * create a buffer and put it in the list of
418  * all buffers. Return pointer to the BUFFER
419  * block for the buffer.
420  */
421 BUFFER *
422 bfind(const char *bname, int cflag)
423 {
424 	BUFFER	*bp;
425 	LINE	*lp;
426 	int	 i;
427 
428 	bp = bheadp;
429 	while (bp != NULL) {
430 		if (strcmp(bname, bp->b_bname) == 0)
431 			return bp;
432 		bp = bp->b_bufp;
433 	}
434 	if (cflag != TRUE)
435 		return NULL;
436 
437 	if ((bp = malloc(sizeof(BUFFER))) == NULL) {
438 		ewprintf("Can't get %d bytes", sizeof(BUFFER));
439 		return NULL;
440 	}
441 	if ((bp->b_bname = strdup(bname)) == NULL) {
442 		ewprintf("Can't get %d bytes", strlen(bname) + 1);
443 		free(bp);
444 		return NULL;
445 	}
446 	if ((lp = lalloc(0)) == NULL) {
447 		free((char *) bp->b_bname);
448 		free(bp);
449 		return NULL;
450 	}
451 	bp->b_altb = bp->b_bufp = NULL;
452 	bp->b_dotp = lp;
453 	bp->b_doto = 0;
454 	bp->b_markp = NULL;
455 	bp->b_marko = 0;
456 	bp->b_flag = defb_flag;
457 	bp->b_nwnd = 0;
458 	bp->b_linep = lp;
459 	bp->b_nmodes = defb_nmodes;
460 	LIST_INIT(&bp->b_undo);
461 	bp->b_undoptr = NULL;
462 	memset(&bp->b_undopos, 0, sizeof bp->b_undopos);
463 	i = 0;
464 	do {
465 		bp->b_modes[i] = defb_modes[i];
466 	} while (i++ < defb_nmodes);
467 	bp->b_fname[0] = '\0';
468 	bzero(&bp->b_fi, sizeof(bp->b_fi));
469 	lp->l_fp = lp;
470 	lp->l_bp = lp;
471 	bp->b_bufp = bheadp;
472 	bheadp = bp;
473 	return bp;
474 }
475 
476 /*
477  * This routine blows away all of the text
478  * in a buffer. If the buffer is marked as changed
479  * then we ask if it is ok to blow it away; this is
480  * to save the user the grief of losing text. The
481  * window chain is nearly always wrong if this gets
482  * called; the caller must arrange for the updates
483  * that are required. Return TRUE if everything
484  * looks good.
485  */
486 int
487 bclear(BUFFER *bp)
488 {
489 	LINE  *lp;
490 	int    s;
491 
492 	if ((bp->b_flag & BFCHG) != 0 &&	/* Changed.		 */
493 	    (s = eyesno("Buffer modified; kill anyway")) != TRUE)
494 		return (s);
495 	bp->b_flag &= ~BFCHG;	/* Not changed		 */
496 	while ((lp = lforw(bp->b_linep)) != bp->b_linep)
497 		lfree(lp);
498 	bp->b_dotp = bp->b_linep;	/* Fix "."		 */
499 	bp->b_doto = 0;
500 	bp->b_markp = NULL;	/* Invalidate "mark"	 */
501 	bp->b_marko = 0;
502 	return TRUE;
503 }
504 
505 /*
506  * Display the given buffer in the given window. Flags indicated
507  * action on redisplay.
508  */
509 int
510 showbuffer(BUFFER *bp, MGWIN *wp, int flags)
511 {
512 	BUFFER *obp;
513 	MGWIN  *owp;
514 
515 	if (wp->w_bufp == bp) {	/* Easy case!	 */
516 		wp->w_flag |= flags;
517 		return TRUE;
518 	}
519 	/* First, dettach the old buffer from the window */
520 	if ((bp->b_altb = obp = wp->w_bufp) != NULL) {
521 		if (--obp->b_nwnd == 0) {
522 			obp->b_dotp = wp->w_dotp;
523 			obp->b_doto = wp->w_doto;
524 			obp->b_markp = wp->w_markp;
525 			obp->b_marko = wp->w_marko;
526 		}
527 	}
528 	/* Now, attach the new buffer to the window */
529 	wp->w_bufp = bp;
530 
531 	if (bp->b_nwnd++ == 0) {	/* First use.		 */
532 		wp->w_dotp = bp->b_dotp;
533 		wp->w_doto = bp->b_doto;
534 		wp->w_markp = bp->b_markp;
535 		wp->w_marko = bp->b_marko;
536 	} else
537 		/* already on screen, steal values from other window */
538 		for (owp = wheadp; owp != NULL; owp = wp->w_wndp)
539 			if (wp->w_bufp == bp && owp != wp) {
540 				wp->w_dotp = owp->w_dotp;
541 				wp->w_doto = owp->w_doto;
542 				wp->w_markp = owp->w_markp;
543 				wp->w_marko = owp->w_marko;
544 				break;
545 			}
546 	wp->w_flag |= WFMODE | flags;
547 	return TRUE;
548 }
549 
550 /*
551  * Pop the buffer we got passed onto the screen.
552  * Returns a status.
553  */
554 MGWIN *
555 popbuf(BUFFER *bp)
556 {
557 	MGWIN  *wp;
558 
559 	if (bp->b_nwnd == 0) {	/* Not on screen yet.	 */
560 		if ((wp = wpopup()) == NULL)
561 			return NULL;
562 	} else
563 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
564 			if (wp->w_bufp == bp) {
565 				wp->w_flag |= WFHARD | WFFORCE;
566 				return wp;
567 			}
568 	if (showbuffer(bp, wp, WFHARD) != TRUE)
569 		return NULL;
570 	return wp;
571 }
572 
573 /*
574  * Insert another buffer at dot.  Very useful.
575  */
576 /* ARGSUSED */
577 int
578 bufferinsert(int f, int n)
579 {
580 	BUFFER *bp;
581 	LINE   *clp;
582 	int     clo;
583 	int     nline;
584 	int     s;
585 	char    bufn[NBUFN];
586 
587 	/* Get buffer to use from user */
588 	if (curbp->b_altb != NULL)
589 		s = eread("Insert buffer: (default %s) ", bufn, NBUFN,
590 			  EFNEW | EFBUF, &(curbp->b_altb->b_bname), NULL);
591 	else
592 		s = eread("Insert buffer: ", bufn, NBUFN, EFNEW | EFBUF, NULL);
593 	if (s == ABORT)
594 		return (s);
595 	if (s == FALSE && curbp->b_altb != NULL)
596 		bp = curbp->b_altb;
597 	else if ((bp = bfind(bufn, FALSE)) == NULL)
598 		return FALSE;
599 
600 	if (bp == curbp) {
601 		ewprintf("Cannot insert buffer into self");
602 		return FALSE;
603 	}
604 	/* insert the buffer */
605 	nline = 0;
606 	clp = lforw(bp->b_linep);
607 	for (;;) {
608 		for (clo = 0; clo < llength(clp); clo++)
609 			if (linsert(1, lgetc(clp, clo)) == FALSE)
610 				return FALSE;
611 		if ((clp = lforw(clp)) == bp->b_linep)
612 			break;
613 		if (newline(FFRAND, 1) == FALSE)	/* fake newline */
614 			return FALSE;
615 		nline++;
616 	}
617 	if (nline == 1)
618 		ewprintf("[Inserted 1 line]");
619 	else
620 		ewprintf("[Inserted %d lines]", nline);
621 
622 	clp = curwp->w_linep;	/* cosmetic adjustment */
623 	if (curwp->w_dotp == clp) {	/* for offscreen insert */
624 		while (nline-- && lback(clp) != curbp->b_linep)
625 			clp = lback(clp);
626 		curwp->w_linep = clp;	/* adjust framing.	 */
627 		curwp->w_flag |= WFHARD;
628 	}
629 	return (TRUE);
630 }
631 
632 /*
633  * Turn off the dirty bit on this buffer.
634  */
635 /* ARGSUSED */
636 int
637 notmodified(int f, int n)
638 {
639 	MGWIN *wp;
640 
641 	curbp->b_flag &= ~BFCHG;
642 	wp = wheadp;		/* Update mode lines.	 */
643 	while (wp != NULL) {
644 		if (wp->w_bufp == curbp)
645 			wp->w_flag |= WFMODE;
646 		wp = wp->w_wndp;
647 	}
648 	ewprintf("Modification-flag cleared");
649 	return TRUE;
650 }
651 
652 #ifndef NO_HELP
653 /*
654  * Popbuf and set all windows to top of buffer.	 Currently only used by
655  * help functions.
656  */
657 int
658 popbuftop(BUFFER *bp)
659 {
660 	MGWIN *wp;
661 
662 	bp->b_dotp = lforw(bp->b_linep);
663 	bp->b_doto = 0;
664 	if (bp->b_nwnd != 0) {
665 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
666 			if (wp->w_bufp == bp) {
667 				wp->w_dotp = bp->b_dotp;
668 				wp->w_doto = 0;
669 				wp->w_flag |= WFHARD;
670 			}
671 	}
672 	return popbuf(bp) != NULL;
673 }
674 #endif
675