xref: /openbsd-src/usr.bin/mg/buffer.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
1 
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *		Buffer handling.
7  */
8 
9 #include <sys/queue.h>
10 #include <errno.h>
11 #include <libgen.h>
12 #include <signal.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 
19 #include "def.h"
20 #include "kbd.h"		/* needed for modes */
21 
22 #define DIFFTOOL "/usr/bin/diff"
23 
24 static struct buffer  *makelist(void);
25 static struct buffer *bnew(const char *);
26 
27 static int usebufname(const char *);
28 
29 /* Flag for global working dir */
30 extern int globalwd;
31 
32 /* ARGSUSED */
33 int
34 togglereadonlyall(int f, int n)
35 {
36 	struct buffer *bp = NULL;
37 	int len = 0;
38 
39 	allbro = !allbro;
40 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
41 		len = strlen(bp->b_bname);
42 		if (bp->b_bname[0] != '*' && bp->b_bname[len - 1] != '*') {
43 			if (allbro)
44 				bp->b_flag |= BFREADONLY;
45 			else
46 				bp->b_flag &= ~BFREADONLY;
47 		}
48 	}
49 	curwp->w_rflag |= WFMODE;
50 
51 	return (TRUE);
52 }
53 
54 /* ARGSUSED */
55 int
56 togglereadonly(int f, int n)
57 {
58 	int s;
59 
60 	if ((s = checkdirty(curbp)) != TRUE)
61 		return (s);
62 	if (!(curbp->b_flag & BFREADONLY))
63 		curbp->b_flag |= BFREADONLY;
64 	else {
65 		curbp->b_flag &= ~BFREADONLY;
66 		if (curbp->b_flag & BFCHG)
67 			ewprintf("Warning: Buffer was modified");
68 	}
69 	curwp->w_rflag |= WFMODE;
70 
71 	return (TRUE);
72 }
73 
74 /* Switch to the named buffer.
75  * If no name supplied, switch to the default (alternate) buffer.
76  */
77 int
78 usebufname(const char *bufp)
79 {
80 	struct buffer *bp = NULL;
81 
82 	if (bufp == NULL) {
83 		if ((bp = bfind("*scratch*", TRUE)) == NULL)
84 			return(FALSE);
85 	} else if (bufp[0] == '\0' && curbp->b_altb != NULL)
86 			bp = curbp->b_altb;
87 	else if ((bp = bfind(bufp, TRUE)) == NULL)
88 		return (FALSE);
89 
90 	/* and put it in current window */
91 	curbp = bp;
92 	return (showbuffer(bp, curwp, WFFRAME | WFFULL));
93 }
94 
95 /*
96  * Attach a buffer to a window. The values of dot and mark come
97  * from the buffer if the use count is 0. Otherwise, they come
98  * from some other window.  *scratch* is the default alternate
99  * buffer.
100  */
101 /* ARGSUSED */
102 int
103 usebuffer(int f, int n)
104 {
105 	char    bufn[NBUFN], *bufp;
106 
107 	/* Get buffer to use from user */
108 	if (curbp->b_altb == NULL)
109 		bufp = eread("Switch to buffer: ", bufn, NBUFN, EFNEW | EFBUF);
110 	else
111 		bufp = eread("Switch to buffer (default %s): ", bufn, NBUFN,
112 		    EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
113 
114 	if (bufp == NULL)
115 		return (ABORT);
116 
117 	return (usebufname(bufp));
118 }
119 
120 /*
121  * pop to buffer asked for by the user.
122  */
123 /* ARGSUSED */
124 int
125 poptobuffer(int f, int n)
126 {
127 	struct buffer *bp;
128 	struct mgwin  *wp;
129 	char    bufn[NBUFN], *bufp;
130 
131 	/* Get buffer to use from user */
132 	if ((curbp->b_altb == NULL) &&
133 	    ((curbp->b_altb = bfind("*scratch*", TRUE)) == NULL))
134 		bufp = eread("Switch to buffer in other window: ", bufn, NBUFN,
135 		    EFNEW | EFBUF);
136 	else
137 		bufp = eread("Switch to buffer in other window (default %s): ",
138 		    bufn, NBUFN, EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
139 	if (bufp == NULL)
140 		return (ABORT);
141 	if (bufp[0] == '\0' && curbp->b_altb != NULL)
142 		bp = curbp->b_altb;
143 	else if ((bp = bfind(bufn, TRUE)) == NULL)
144 		return (FALSE);
145 	if (bp == curbp)
146 		return (splitwind(f, n));
147 	/* and put it in a new, non-ephemeral window */
148 	if ((wp = popbuf(bp, WNONE)) == NULL)
149 		return (FALSE);
150 	curbp = bp;
151 	curwp = wp;
152 	return (TRUE);
153 }
154 
155 /*
156  * Dispose of a buffer, by name.
157  * Ask for the name (unless called by dired mode). Look it up (don't
158  * get too upset if it isn't there at all!). Clear the buffer (ask
159  * if the buffer has been changed). Then free the header
160  * line and the buffer header. Bound to "C-x k".
161  */
162 /* ARGSUSED */
163 int
164 killbuffer_cmd(int f, int n)
165 {
166 	struct buffer *bp;
167 	char    bufn[NBUFN], *bufp;
168 
169 	if (f & FFRAND) /* dired mode 'q' */
170 		bp = curbp;
171 	else if ((bufp = eread("Kill buffer (default %s): ", bufn, NBUFN,
172 	    EFNUL | EFNEW | EFBUF, curbp->b_bname)) == NULL)
173 		return (ABORT);
174 	else if (bufp[0] == '\0')
175 		bp = curbp;
176 	else if ((bp = bfind(bufn, FALSE)) == NULL)
177 		return (FALSE);
178 	return (killbuffer(bp));
179 }
180 
181 int
182 killbuffer(struct buffer *bp)
183 {
184 	struct buffer *bp1;
185 	struct buffer *bp2;
186 	struct mgwin  *wp;
187 	int s;
188 	struct undo_rec *rec;
189 
190 	/*
191 	 * Find some other buffer to display. Try the alternate buffer,
192 	 * then the first different buffer in the buffer list.  If there's
193 	 * only one buffer, create buffer *scratch* and make it the alternate
194 	 * buffer.  Return if *scratch* is only buffer...
195 	 */
196 	if ((bp1 = bp->b_altb) == NULL) {
197 		/* only one buffer. see if it's *scratch* */
198 		if (bp == bfind("*scratch*", FALSE))
199 			return (TRUE);
200 		/* create *scratch* for alternate buffer */
201 		if ((bp1 = bfind("*scratch*", TRUE)) == NULL)
202 			return (FALSE);
203 	}
204 	if ((s = bclear(bp)) != TRUE)
205 		return (s);
206 	for (wp = wheadp; bp->b_nwnd > 0; wp = wp->w_wndp) {
207 		if (wp->w_bufp == bp) {
208 			bp2 = bp1->b_altb;	/* save alternate buffer */
209 			if (showbuffer(bp1, wp, WFMODE | WFFRAME | WFFULL))
210 				bp1->b_altb = bp2;
211 			else
212 				bp1 = bp2;
213 		}
214 	}
215 	if (bp == curbp)
216 		curbp = bp1;
217 	free(bp->b_headp);			/* Release header line.  */
218 	bp2 = NULL;				/* Find the header.	 */
219 	bp1 = bheadp;
220 	while (bp1 != bp) {
221 		if (bp1->b_altb == bp)
222 			bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
223 		bp2 = bp1;
224 		bp1 = bp1->b_bufp;
225 	}
226 	bp1 = bp1->b_bufp;			/* Next one in chain.	 */
227 	if (bp2 == NULL)			/* Unlink it.		 */
228 		bheadp = bp1;
229 	else
230 		bp2->b_bufp = bp1;
231 	while (bp1 != NULL) {			/* Finish with altb's	 */
232 		if (bp1->b_altb == bp)
233 			bp1->b_altb = (bp->b_altb == bp1) ? NULL : bp->b_altb;
234 		bp1 = bp1->b_bufp;
235 	}
236 
237 	while ((rec = TAILQ_FIRST(&bp->b_undo))) {
238 		TAILQ_REMOVE(&bp->b_undo, rec, next);
239 		free_undo_record(rec);
240 	}
241 
242 	free(bp->b_bname);			/* Release name block	 */
243 	free(bp);				/* Release buffer block */
244 	return (TRUE);
245 }
246 
247 /*
248  * Save some buffers - just call anycb with the arg flag.
249  */
250 /* ARGSUSED */
251 int
252 savebuffers(int f, int n)
253 {
254 	if (anycb(f) == ABORT)
255 		return (ABORT);
256 	return (TRUE);
257 }
258 
259 /*
260  * Listing buffers.
261  */
262 static int listbuf_ncol;
263 
264 static int	listbuf_goto_buffer(int f, int n);
265 static int	listbuf_goto_buffer_one(int f, int n);
266 static int	listbuf_goto_buffer_helper(int f, int n, int only);
267 
268 static PF listbuf_pf[] = {
269 	listbuf_goto_buffer
270 };
271 static PF listbuf_one[] = {
272 	listbuf_goto_buffer_one
273 };
274 
275 
276 static struct KEYMAPE (2) listbufmap = {
277 	2,
278 	2,
279 	rescan,
280 	{
281 		{
282 			CCHR('M'), CCHR('M'), listbuf_pf, NULL
283 		},
284 		{
285 			'1', '1', listbuf_one, NULL
286 		}
287 	}
288 };
289 
290 /*
291  * Display the buffer list. This is done
292  * in two parts. The "makelist" routine figures out
293  * the text, and puts it in a buffer. "popbuf"
294  * then pops the data onto the screen. Bound to
295  * "C-x C-b".
296  */
297 /* ARGSUSED */
298 int
299 listbuffers(int f, int n)
300 {
301 	static int		 initialized = 0;
302 	struct buffer		*bp;
303 	struct mgwin		*wp;
304 
305 	if (!initialized) {
306 		maps_add((KEYMAP *)&listbufmap, "listbufmap");
307 		initialized = 1;
308 	}
309 
310 	if ((bp = makelist()) == NULL || (wp = popbuf(bp, WNONE)) == NULL)
311 		return (FALSE);
312 	wp->w_dotp = bp->b_dotp; /* fix up if window already on screen */
313 	wp->w_doto = bp->b_doto;
314 	bp->b_modes[0] = name_mode("fundamental");
315 	bp->b_modes[1] = name_mode("listbufmap");
316 	bp->b_nmodes = 1;
317 
318 	return (TRUE);
319 }
320 
321 /*
322  * This routine rebuilds the text for the
323  * list buffers command. Return pointer
324  * to new list if everything works.
325  * Return NULL if there is an error (if
326  * there is no memory).
327  */
328 static struct buffer *
329 makelist(void)
330 {
331 	int		w = ncol / 2;
332 	struct buffer	*bp, *blp;
333 	struct line	*lp;
334 
335 	if ((blp = bfind("*Buffer List*", TRUE)) == NULL)
336 		return (NULL);
337 	if (bclear(blp) != TRUE)
338 		return (NULL);
339 	blp->b_flag &= ~BFCHG;		/* Blow away old.	 */
340 	blp->b_flag |= BFREADONLY;
341 
342 	listbuf_ncol = ncol;		/* cache ncol for listbuf_goto_buffer */
343 
344 	if (addlinef(blp, "%-*s%s", w, " MR Buffer", "Size   File") == FALSE ||
345 	    addlinef(blp, "%-*s%s", w, " -- ------", "----   ----") == FALSE)
346 		return (NULL);
347 
348 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
349 		RSIZE nbytes;
350 
351 		nbytes = 0;			/* Count bytes in buf.	 */
352 		if (bp != blp) {
353 			lp = bfirstlp(bp);
354 			while (lp != bp->b_headp) {
355 				nbytes += llength(lp) + 1;
356 				lp = lforw(lp);
357 			}
358 			if (nbytes)
359 				nbytes--;	/* no bonus newline	 */
360 		}
361 
362 		if (addlinef(blp, "%c%c%c %-*.*s%c%-6d %-*s",
363 		    (bp == curbp) ? '.' : ' ',	/* current buffer ? */
364 		    ((bp->b_flag & BFCHG) != 0) ? '*' : ' ',	/* changed ? */
365 		    ((bp->b_flag & BFREADONLY) != 0) ? ' ' : '*',
366 		    w - 5,		/* four chars already written */
367 		    w - 5,		/* four chars already written */
368 		    bp->b_bname,	/* buffer name */
369 		    strlen(bp->b_bname) < w - 5 ? ' ' : '$', /* truncated? */
370 		    nbytes,		/* buffer size */
371 		    w - 7,		/* seven chars already written */
372 		    bp->b_fname) == FALSE)
373 			return (NULL);
374 	}
375 	blp->b_dotp = bfirstlp(blp);		/* put dot at beginning of
376 						 * buffer */
377 	blp->b_doto = 0;
378 	return (blp);				/* All done		 */
379 }
380 
381 static int
382 listbuf_goto_buffer(int f, int n)
383 {
384 	return (listbuf_goto_buffer_helper(f, n, 0));
385 }
386 
387 static int
388 listbuf_goto_buffer_one(int f, int n)
389 {
390 	return (listbuf_goto_buffer_helper(f, n, 1));
391 }
392 
393 static int
394 listbuf_goto_buffer_helper(int f, int n, int only)
395 {
396 	struct buffer	*bp;
397 	struct mgwin	*wp;
398 	char		*line = NULL;
399 	int		 i, ret = FALSE;
400 
401 	if (curwp->w_dotp->l_text[listbuf_ncol/2 - 1] == '$') {
402 		dobeep();
403 		ewprintf("buffer name truncated");
404 		return (FALSE);
405 	}
406 
407 	if ((line = malloc(listbuf_ncol/2)) == NULL)
408 		return (FALSE);
409 
410 	memcpy(line, curwp->w_dotp->l_text + 4, listbuf_ncol/2 - 5);
411 	for (i = listbuf_ncol/2 - 6; i > 0; i--) {
412 		if (line[i] != ' ') {
413 			line[i + 1] = '\0';
414 			break;
415 		}
416 	}
417 	if (i == 0)
418 		goto cleanup;
419 
420 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
421 		if (strcmp(bp->b_bname, line) == 0)
422 			break;
423 	}
424 	if (bp == NULL)
425 		goto cleanup;
426 
427 	if ((wp = popbuf(bp, WNONE)) == NULL)
428 		goto cleanup;
429 	curbp = bp;
430 	curwp = wp;
431 
432 	if (only)
433 		ret = (onlywind(FFRAND, 1));
434 	else
435 		ret = TRUE;
436 
437 cleanup:
438 	free(line);
439 
440 	return (ret);
441 }
442 
443 /*
444  * The argument "fmt" points to a format string.  Append this line to the
445  * buffer. Handcraft the EOL on the end.  Return TRUE if it worked and
446  * FALSE if you ran out of room.
447  */
448 int
449 addlinef(struct buffer *bp, char *fmt, ...)
450 {
451 	va_list		 ap;
452 	struct line	*lp;
453 
454 	if ((lp = lalloc(0)) == NULL)
455 		return (FALSE);
456 	va_start(ap, fmt);
457 	if (vasprintf(&lp->l_text, fmt, ap) == -1) {
458 		lfree(lp);
459 		va_end(ap);
460 		return (FALSE);
461 	}
462 	lp->l_used = strlen(lp->l_text);
463 	va_end(ap);
464 
465 	bp->b_headp->l_bp->l_fp = lp;		/* Hook onto the end	 */
466 	lp->l_bp = bp->b_headp->l_bp;
467 	bp->b_headp->l_bp = lp;
468 	lp->l_fp = bp->b_headp;
469 	bp->b_lines++;
470 
471 	return (TRUE);
472 }
473 
474 /*
475  * Look through the list of buffers, giving the user a chance to save them.
476  * Return TRUE if there are any changed buffers afterwards.  Buffers that don't
477  * have an associated file don't count.  Return FALSE if there are no changed
478  * buffers.  Return ABORT if an error occurs or if the user presses c-g.
479  */
480 int
481 anycb(int f)
482 {
483 	struct buffer	*bp;
484 	int		 s = FALSE, save = FALSE, save2 = FALSE, ret;
485 	char		 pbuf[NFILEN + 11];
486 
487 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
488 		if (*(bp->b_fname) != '\0' && (bp->b_flag & BFCHG) != 0) {
489 			ret = snprintf(pbuf, sizeof(pbuf), "Save file %s",
490 			    bp->b_fname);
491 			if (ret < 0 || ret >= sizeof(pbuf)) {
492 				dobeep();
493 				ewprintf("Error: filename too long!");
494 				return (UERROR);
495 			}
496 			if ((f == TRUE || (save = eyorn(pbuf)) == TRUE) &&
497 			    (save2 = buffsave(bp)) == TRUE) {
498 				bp->b_flag &= ~BFCHG;
499 				upmodes(bp);
500 			} else {
501 				if (save2 == FIOERR)
502 					return (save2);
503 				s = TRUE;
504 			}
505 			if (save == ABORT)
506 				return (save);
507 			save = TRUE;
508 		}
509 	}
510 	if (save == FALSE /* && kbdmop == NULL */ )	/* experimental */
511 		ewprintf("(No files need saving)");
512 	return (s);
513 }
514 
515 /*
516  * Search for a buffer, by name.
517  * If not found, and the "cflag" is TRUE,
518  * create a new buffer. Return pointer to the found
519  * (or new) buffer.
520  */
521 struct buffer *
522 bfind(const char *bname, int cflag)
523 {
524 	struct buffer	*bp;
525 
526 	bp = bheadp;
527 	while (bp != NULL) {
528 		if (strcmp(bname, bp->b_bname) == 0)
529 			return (bp);
530 		bp = bp->b_bufp;
531 	}
532 	if (cflag != TRUE)
533 		return (NULL);
534 
535 	bp = bnew(bname);
536 
537 	return (bp);
538 }
539 
540 /*
541  * Create a new buffer and put it in the list of
542  * all buffers.
543  */
544 static struct buffer *
545 bnew(const char *bname)
546 {
547 	struct buffer	*bp;
548 	struct line	*lp;
549 	int		 i;
550 	size_t		len;
551 
552 	bp = calloc(1, sizeof(struct buffer));
553 	if (bp == NULL) {
554 		dobeep();
555 		ewprintf("Can't get %d bytes", sizeof(struct buffer));
556 		return (NULL);
557 	}
558 	if ((lp = lalloc(0)) == NULL) {
559 		free(bp);
560 		return (NULL);
561 	}
562 	bp->b_altb = bp->b_bufp = NULL;
563 	bp->b_dotp = lp;
564 	bp->b_doto = 0;
565 	bp->b_markp = NULL;
566 	bp->b_marko = 0;
567 	bp->b_flag = defb_flag;
568 	/* if buffer name starts and ends with '*', we ignore changes */
569 	len = strlen(bname);
570 	if (len) {
571 		if (bname[0] == '*' && bname[len - 1] == '*')
572 			bp->b_flag |= BFIGNDIRTY;
573 	}
574 	bp->b_nwnd = 0;
575 	bp->b_headp = lp;
576 	bp->b_nmodes = defb_nmodes;
577 	TAILQ_INIT(&bp->b_undo);
578 	bp->b_undoptr = NULL;
579 	i = 0;
580 	do {
581 		bp->b_modes[i] = defb_modes[i];
582 	} while (i++ < defb_nmodes);
583 	bp->b_fname[0] = '\0';
584 	bp->b_cwd[0] = '\0';
585 	bzero(&bp->b_fi, sizeof(bp->b_fi));
586 	lp->l_fp = lp;
587 	lp->l_bp = lp;
588 	bp->b_bufp = bheadp;
589 	bheadp = bp;
590 	bp->b_dotline = bp->b_markline = 1;
591 	bp->b_lines = 1;
592 	if ((bp->b_bname = strdup(bname)) == NULL) {
593 		dobeep();
594 		ewprintf("Can't get %d bytes", strlen(bname) + 1);
595 		return (NULL);
596 	}
597 
598 	return (bp);
599 }
600 
601 /*
602  * This routine blows away all of the text
603  * in a buffer. If the buffer is marked as changed
604  * then we ask if it is ok to blow it away; this is
605  * to save the user the grief of losing text. The
606  * window chain is nearly always wrong if this gets
607  * called; the caller must arrange for the updates
608  * that are required. Return TRUE if everything
609  * looks good.
610  */
611 int
612 bclear(struct buffer *bp)
613 {
614 	struct line	*lp;
615 	int		 s;
616 
617 	/* Has buffer changed, and do we care? */
618 	if (!(bp->b_flag & BFIGNDIRTY) && (bp->b_flag & BFCHG) != 0 &&
619 	    (s = eyesno("Buffer modified; kill anyway")) != TRUE)
620 		return (s);
621 	bp->b_flag &= ~BFCHG;	/* Not changed		 */
622 	while ((lp = lforw(bp->b_headp)) != bp->b_headp)
623 		lfree(lp);
624 	bp->b_dotp = bp->b_headp;	/* Fix dot */
625 	bp->b_doto = 0;
626 	bp->b_markp = NULL;	/* Invalidate "mark"	 */
627 	bp->b_marko = 0;
628 	bp->b_dotline = bp->b_markline = 1;
629 	bp->b_lines = 1;
630 
631 	return (TRUE);
632 }
633 
634 /*
635  * Display the given buffer in the given window. Flags indicated
636  * action on redisplay. Update modified flag so insert loop can check it.
637  */
638 int
639 showbuffer(struct buffer *bp, struct mgwin *wp, int flags)
640 {
641 	struct buffer	*obp;
642 	struct mgwin	*owp;
643 
644 	/* Ensure file has not been modified elsewhere */
645 	if (fchecktime(bp) != TRUE)
646 		bp->b_flag |= BFDIRTY;
647 
648 	if (wp->w_bufp == bp) {	/* Easy case! */
649 		wp->w_rflag |= flags;
650 		return (TRUE);
651 	}
652 	/* First, detach the old buffer from the window */
653 	if ((bp->b_altb = obp = wp->w_bufp) != NULL) {
654 		if (--obp->b_nwnd == 0) {
655 			obp->b_dotp = wp->w_dotp;
656 			obp->b_doto = wp->w_doto;
657 			obp->b_markp = wp->w_markp;
658 			obp->b_marko = wp->w_marko;
659 			obp->b_dotline = wp->w_dotline;
660 			obp->b_markline = wp->w_markline;
661 		}
662 	}
663 	/* Now, attach the new buffer to the window */
664 	wp->w_bufp = bp;
665 
666 	if (bp->b_nwnd++ == 0) {	/* First use.		 */
667 		wp->w_dotp = bp->b_dotp;
668 		wp->w_doto = bp->b_doto;
669 		wp->w_markp = bp->b_markp;
670 		wp->w_marko = bp->b_marko;
671 		wp->w_dotline = bp->b_dotline;
672 		wp->w_markline = bp->b_markline;
673 	} else
674 		/* already on screen, steal values from other window */
675 		for (owp = wheadp; owp != NULL; owp = wp->w_wndp)
676 			if (wp->w_bufp == bp && owp != wp) {
677 				wp->w_dotp = owp->w_dotp;
678 				wp->w_doto = owp->w_doto;
679 				wp->w_markp = owp->w_markp;
680 				wp->w_marko = owp->w_marko;
681 				wp->w_dotline = owp->w_dotline;
682 				wp->w_markline = owp->w_markline;
683 				break;
684 			}
685 	wp->w_rflag |= WFMODE | flags;
686 	return (TRUE);
687 }
688 
689 /*
690  * Augment a buffer name with a number, if necessary
691  *
692  * If more than one file of the same basename() is open,
693  * the additional buffers are named "file<2>", "file<3>", and
694  * so forth.  This function adjusts a buffer name to
695  * include the number, if necessary.
696  */
697 int
698 augbname(char *bn, const char *fn, size_t bs)
699 {
700 	int	 count;
701 	size_t	 remain, len;
702 
703 	if ((len = xbasename(bn, fn, bs)) >= bs)
704 		return (FALSE);
705 
706 	remain = bs - len;
707 	for (count = 2; bfind(bn, FALSE) != NULL; count++)
708 		snprintf(bn + len, remain, "<%d>", count);
709 
710 	return (TRUE);
711 }
712 
713 /*
714  * Pop the buffer we got passed onto the screen.
715  * Returns a status.
716  */
717 struct mgwin *
718 popbuf(struct buffer *bp, int flags)
719 {
720 	struct mgwin	*wp;
721 
722 	if (bp->b_nwnd == 0) {	/* Not on screen yet.	 */
723 		/*
724 		 * Pick a window for a pop-up.
725 		 * If only one window, split the screen.
726 		 * Flag the new window as ephemeral
727 		 */
728 		if (wheadp->w_wndp == NULL &&
729 		    splitwind(FFOTHARG, flags) == FALSE)
730  			return (NULL);
731 
732 		/*
733 		 * Pick the uppermost window that isn't
734 		 * the current window. An LRU algorithm
735 		 * might be better. Return a pointer, or NULL on error.
736 		 */
737 		wp = wheadp;
738 
739 		while (wp != NULL && wp == curwp)
740 			wp = wp->w_wndp;
741 	} else {
742 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
743 			if (wp->w_bufp == bp) {
744 				wp->w_rflag |= WFFULL | WFFRAME;
745 				return (wp);
746 			}
747 		}
748 	}
749 	if (!wp)
750 		return (NULL);
751 
752 	if (showbuffer(bp, wp, WFFULL) != TRUE)
753 		return (NULL);
754 	return (wp);
755 }
756 
757 /*
758  * Insert another buffer at dot.  Very useful.
759  */
760 /* ARGSUSED */
761 int
762 bufferinsert(int f, int n)
763 {
764 	struct buffer *bp;
765 	struct line   *clp;
766 	int	clo, nline;
767 	char	bufn[NBUFN], *bufp;
768 
769 	/* Get buffer to use from user */
770 	if (curbp->b_altb != NULL)
771 		bufp = eread("Insert buffer (default %s): ", bufn, NBUFN,
772 		    EFNUL | EFNEW | EFBUF, curbp->b_altb->b_bname);
773 	else
774 		bufp = eread("Insert buffer: ", bufn, NBUFN, EFNEW | EFBUF);
775 	if (bufp == NULL)
776 		return (ABORT);
777 	if (bufp[0] == '\0' && curbp->b_altb != NULL)
778 		bp = curbp->b_altb;
779 	else if ((bp = bfind(bufn, FALSE)) == NULL)
780 		return (FALSE);
781 
782 	if (bp == curbp) {
783 		dobeep();
784 		ewprintf("Cannot insert buffer into self");
785 		return (FALSE);
786 	}
787 	/* insert the buffer */
788 	nline = 0;
789 	clp = bfirstlp(bp);
790 	for (;;) {
791 		for (clo = 0; clo < llength(clp); clo++)
792 			if (linsert(1, lgetc(clp, clo)) == FALSE)
793 				return (FALSE);
794 		if ((clp = lforw(clp)) == bp->b_headp)
795 			break;
796 		if (enewline(FFRAND, 1) == FALSE)	/* fake newline */
797 			return (FALSE);
798 		nline++;
799 	}
800 	if (nline == 1)
801 		ewprintf("[Inserted 1 line]");
802 	else
803 		ewprintf("[Inserted %d lines]", nline);
804 
805 	clp = curwp->w_linep;		/* cosmetic adjustment	*/
806 	if (curwp->w_dotp == clp) {	/* for offscreen insert */
807 		while (nline-- && lback(clp) != curbp->b_headp)
808 			clp = lback(clp);
809 		curwp->w_linep = clp;	/* adjust framing.	*/
810 		curwp->w_rflag |= WFFULL;
811 	}
812 	return (TRUE);
813 }
814 
815 /*
816  * Turn off the dirty bit on this buffer.
817  */
818 /* ARGSUSED */
819 int
820 notmodified(int f, int n)
821 {
822 	struct mgwin *wp;
823 
824 	curbp->b_flag &= ~BFCHG;
825 	wp = wheadp;		/* Update mode lines.	 */
826 	while (wp != NULL) {
827 		if (wp->w_bufp == curbp)
828 			wp->w_rflag |= WFMODE;
829 		wp = wp->w_wndp;
830 	}
831 	ewprintf("Modification-flag cleared");
832 	return (TRUE);
833 }
834 
835 /*
836  * Popbuf and set all windows to top of buffer.
837  */
838 int
839 popbuftop(struct buffer *bp, int flags)
840 {
841 	struct mgwin *wp;
842 
843 	bp->b_dotp = bfirstlp(bp);
844 	bp->b_doto = 0;
845 	if (bp->b_nwnd != 0) {
846 		for (wp = wheadp; wp != NULL; wp = wp->w_wndp)
847 			if (wp->w_bufp == bp) {
848 				wp->w_dotp = bp->b_dotp;
849 				wp->w_doto = 0;
850 				wp->w_rflag |= WFFULL;
851 			}
852 	}
853 	return (popbuf(bp, flags) != NULL);
854 }
855 
856 /*
857  * Return the working directory for the current buffer, terminated
858  * with a '/'. First, try to extract it from the current buffer's
859  * filename. If that fails, use global cwd.
860  */
861 int
862 getbufcwd(char *path, size_t plen)
863 {
864 	char cwd[NFILEN];
865 
866 	if (plen == 0)
867 		return (FALSE);
868 
869 	if (globalwd == FALSE && curbp->b_cwd[0] != '\0') {
870 		(void)strlcpy(path, curbp->b_cwd, plen);
871 	} else {
872 		if (getcwdir(cwd, sizeof(cwd)) == FALSE)
873 			goto error;
874 		(void)strlcpy(path, cwd, plen);
875 	}
876 	return (TRUE);
877 error:
878 	path[0] = '\0';
879 	return (FALSE);
880 }
881 
882 /*
883  * Ensures a buffer has not been modified elsewhere; e.g. on disk.
884  * Prompt the user if it has.
885  * Returns TRUE if it has NOT (i.e. buffer is ok to edit).
886  * FALSE or ABORT otherwise
887  */
888 int
889 checkdirty(struct buffer *bp)
890 {
891 	int s;
892 
893 	if ((bp->b_flag & (BFCHG | BFDIRTY)) == 0)
894 		if (fchecktime(bp) != TRUE)
895 			bp->b_flag |= BFDIRTY;
896 
897 	if ((bp->b_flag & (BFDIRTY | BFIGNDIRTY)) == BFDIRTY) {
898 		s = eynorr("File changed on disk; really edit the buffer");
899 		switch (s) {
900 		case TRUE:
901 			bp->b_flag &= ~BFDIRTY;
902 			bp->b_flag |= BFIGNDIRTY;
903 			return (TRUE);
904 		case REVERT:
905 			dorevert();
906 			return (FALSE);
907 		default:
908 			return (s);
909 		}
910 	}
911 
912 	return (TRUE);
913 }
914 
915 /*
916  * Revert the current buffer to whatever is on disk.
917  */
918 /* ARGSUSED */
919 int
920 revertbuffer(int f, int n)
921 {
922 	char fbuf[NFILEN + 32];
923 
924 	if (curbp->b_fname[0] == 0) {
925 		dobeep();
926 		ewprintf("Cannot revert buffer not associated with any files.");
927 		return (FALSE);
928 	}
929 
930 	snprintf(fbuf, sizeof(fbuf), "Revert buffer from file %s",
931 	    curbp->b_fname);
932 
933 	if (eyorn(fbuf) == TRUE)
934 		return dorevert();
935 
936 	return (FALSE);
937 }
938 
939 int
940 dorevert(void)
941 {
942 	int lineno;
943 	struct undo_rec *rec;
944 
945 	if (access(curbp->b_fname, F_OK|R_OK) != 0) {
946 		dobeep();
947 		if (errno == ENOENT)
948 			ewprintf("File %s no longer exists!",
949 			    curbp->b_fname);
950 		else
951 			ewprintf("File %s is no longer readable!",
952 			    curbp->b_fname);
953 		return (FALSE);
954 	}
955 
956 	/* Save our current line, so we can go back after reloading. */
957 	lineno = curwp->w_dotline;
958 
959 	/* Prevent readin from asking if we want to kill the buffer. */
960 	curbp->b_flag &= ~BFCHG;
961 
962 	/* Clean up undo memory */
963 	while ((rec = TAILQ_FIRST(&curbp->b_undo))) {
964 		TAILQ_REMOVE(&curbp->b_undo, rec, next);
965 		free_undo_record(rec);
966 	}
967 
968 	if (readin(curbp->b_fname))
969 		return(setlineno(lineno));
970 	return (FALSE);
971 }
972 
973 /*
974  * Diff the current buffer to what is on disk.
975  */
976 /*ARGSUSED */
977 int
978 diffbuffer(int f, int n)
979 {
980 	struct buffer	*bp;
981 	struct line	*lp, *lpend;
982 	size_t		 len;
983 	int		 ret;
984 	char		*text, *ttext;
985 	char		* const argv[] =
986 	    {DIFFTOOL, "-u", "-p", curbp->b_fname, "-", (char *)NULL};
987 
988 	len = 0;
989 
990 	/* C-u is not supported */
991 	if (n > 1)
992 		return (ABORT);
993 
994 	if (access(DIFFTOOL, X_OK) != 0) {
995 		dobeep();
996 		ewprintf("%s not found or not executable.", DIFFTOOL);
997 		return (FALSE);
998 	}
999 
1000 	if (curbp->b_fname[0] == 0) {
1001 		dobeep();
1002 		ewprintf("Cannot diff buffer not associated with any files.");
1003 		return (FALSE);
1004 	}
1005 
1006 	lpend = curbp->b_headp;
1007 	for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) {
1008 		len+=llength(lp);
1009 		if (lforw(lp) != lpend)		/* no implied \n on last line */
1010 			len++;
1011 	}
1012 	if ((text = calloc(len + 1, sizeof(char))) == NULL) {
1013 		dobeep();
1014 		ewprintf("Cannot allocate memory.");
1015 		return (FALSE);
1016 	}
1017 	ttext = text;
1018 
1019 	for (lp = lforw(lpend); lp != lpend; lp = lforw(lp)) {
1020 		if (llength(lp) != 0) {
1021 			memcpy(ttext, ltext(lp), llength(lp));
1022 			ttext += llength(lp);
1023 		}
1024 		if (lforw(lp) != lpend)		/* no implied \n on last line */
1025 			*ttext++ = '\n';
1026 	}
1027 
1028 	bp = bfind("*Diff*", TRUE);
1029 	bp->b_flag |= BFREADONLY;
1030 	if (bclear(bp) != TRUE) {
1031 		free(text);
1032 		return (FALSE);
1033 	}
1034 
1035 	ret = pipeio(DIFFTOOL, argv, text, len, bp);
1036 
1037 	if (ret == TRUE) {
1038 		eerase();
1039 		if (lforw(bp->b_headp) == bp->b_headp)
1040 			addline(bp, "Diff finished (no differences).");
1041 	}
1042 
1043 	free(text);
1044 	return (ret);
1045 }
1046 
1047 /*
1048  * Given a file name, either find the buffer it uses, or create a new
1049  * empty buffer to put it in.
1050  */
1051 struct buffer *
1052 findbuffer(char *fn)
1053 {
1054 	struct buffer	*bp;
1055 	char		bname[NBUFN], fname[NBUFN];
1056 
1057 	if (strlcpy(fname, fn, sizeof(fname)) >= sizeof(fname)) {
1058 		dobeep();
1059 		ewprintf("filename too long");
1060 		return (NULL);
1061 	}
1062 
1063 	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
1064 		if (strcmp(bp->b_fname, fname) == 0)
1065 			return (bp);
1066 	}
1067 	/* Not found. Create a new one, adjusting name first */
1068 	if (augbname(bname, fname, sizeof(bname)) == FALSE)
1069 		return (NULL);
1070 
1071 	bp = bfind(bname, TRUE);
1072 	return (bp);
1073 }
1074