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