1/*-
2 * Copyright (c) 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 1993, 1994, 1995, 1996
5 *	Keith Bostic.  All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10#include "config.h"
11
12#ifndef lint
13static const char sccsid[] = "$Id: vs_split.c,v 10.42 2001/06/25 15:19:38 skimo Exp $";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18#include <sys/time.h>
19
20#include <bitstring.h>
21#include <errno.h>
22#include <limits.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include "../common/common.h"
28#include "vi.h"
29
30typedef enum { HORIZ_FOLLOW, HORIZ_PRECEDE, VERT_FOLLOW, VERT_PRECEDE } jdir_t;
31
32static SCR	*vs_getbg __P((SCR *, char *));
33static void      vs_insert __P((SCR *sp, GS *gp));
34static int	 vs_join __P((SCR *, SCR **, jdir_t *));
35
36/*
37 * vs_split --
38 *	Create a new screen, horizontally.
39 *
40 * PUBLIC: int vs_split __P((SCR *, SCR *, int));
41 */
42int
43vs_split(
44	SCR *sp,
45	SCR *new,
46	int ccl)		/* Colon-command line split. */
47{
48	GS *gp;
49	SMAP *smp;
50	size_t half;
51	int issmallscreen, splitup;
52
53	gp = sp->gp;
54
55	/* Check to see if it's possible. */
56	/* XXX: The IS_ONELINE fix will change this, too. */
57	if (sp->rows < 4) {
58		msgq(sp, M_ERR,
59		    "222|Screen must be larger than %d lines to split", 4 - 1);
60		return (1);
61	}
62
63	/* Wait for any messages in the screen. */
64	vs_resolve(sp, NULL, 1);
65
66	/* Get a new screen map. */
67	CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
68	if (_HMAP(new) == NULL)
69		return (1);
70	_HMAP(new)->lno = sp->lno;
71	_HMAP(new)->coff = 0;
72	_HMAP(new)->soff = 1;
73
74	/* Split the screen in half. */
75	half = sp->rows / 2;
76	if (ccl && half > 6)
77		half = 6;
78
79	/*
80	 * Small screens: see vs_refresh.c section 6a.  Set a flag so
81	 * we know to fix the screen up later.
82	 */
83	issmallscreen = IS_SMALL(sp);
84
85	/* The columns in the screen don't change. */
86	new->coff = sp->coff;
87	new->cols = sp->cols;
88
89	/*
90	 * Split the screen, and link the screens together.  If creating a
91	 * screen to edit the colon command line or the cursor is in the top
92	 * half of the current screen, the new screen goes under the current
93	 * screen.  Else, it goes above the current screen.
94	 *
95	 * Recalculate current cursor position based on sp->lno, we're called
96	 * with the cursor on the colon command line.  Then split the screen
97	 * in half and update the shared information.
98	 */
99	splitup =
100	    !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half;
101	if (splitup) {				/* Old is bottom half. */
102		new->rows = sp->rows - half;	/* New. */
103		new->roff = sp->roff;
104		sp->rows = half;		/* Old. */
105		sp->roff += new->rows;
106
107		/*
108		 * If the parent is the bottom half of the screen, shift
109		 * the map down to match on-screen text.
110		 */
111		memcpy(_HMAP(sp), _HMAP(sp) + new->rows,
112		    (sp->t_maxrows - new->rows) * sizeof(SMAP));
113	} else {				/* Old is top half. */
114		new->rows = half;		/* New. */
115		sp->rows -= half;		/* Old. */
116		new->roff = sp->roff + sp->rows;
117	}
118
119	/* Adjust maximum text count. */
120	sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
121	new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1;
122
123	/*
124	 * Small screens: see vs_refresh.c, section 6a.
125	 *
126	 * The child may have different screen options sizes than the parent,
127	 * so use them.  Guarantee that text counts aren't larger than the
128	 * new screen sizes.
129	 */
130	if (issmallscreen) {
131		/* Fix the text line count for the parent. */
132		if (splitup)
133			sp->t_rows -= new->rows;
134
135		/* Fix the parent screen. */
136		if (sp->t_rows > sp->t_maxrows)
137			sp->t_rows = sp->t_maxrows;
138		if (sp->t_minrows > sp->t_maxrows)
139			sp->t_minrows = sp->t_maxrows;
140
141		/* Fix the child screen. */
142		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
143		if (new->t_rows > new->t_maxrows)
144			new->t_rows = new->t_maxrows;
145		if (new->t_minrows > new->t_maxrows)
146			new->t_minrows = new->t_maxrows;
147	} else {
148		sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
149
150		/*
151		 * The new screen may be a small screen, even if the parent
152		 * was not.  Don't complain if O_WINDOW is too large, we're
153		 * splitting the screen so the screen is much smaller than
154		 * normal.
155		 */
156		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
157		if (new->t_rows > new->rows - 1)
158			new->t_minrows = new->t_rows =
159			    IS_ONELINE(new) ? 1 : new->rows - 1;
160	}
161
162	/* Adjust the ends of the new and old maps. */
163	_TMAP(sp) = IS_ONELINE(sp) ?
164	    _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1);
165	_TMAP(new) = IS_ONELINE(new) ?
166	    _HMAP(new) : _HMAP(new) + (new->t_rows - 1);
167
168	/* Reset the length of the default scroll. */
169	if ((sp->defscroll = sp->t_maxrows / 2) == 0)
170		sp->defscroll = 1;
171	if ((new->defscroll = new->t_maxrows / 2) == 0)
172		new->defscroll = 1;
173
174	/* Fit the screen into the logical chain. */
175	vs_insert(new, sp->gp);
176
177	/* Tell the display that we're splitting. */
178	(void)gp->scr_split(sp, new);
179
180	/*
181	 * Initialize the screen flags:
182	 *
183	 * If we're in vi mode in one screen, we don't have to reinitialize.
184	 * This isn't just a cosmetic fix.  The path goes like this:
185	 *
186	 *	return into vi(), SC_SSWITCH set
187	 *	call vs_refresh() with SC_STATUS set
188	 *	call vs_resolve to display the status message
189	 *	call vs_refresh() because the SC_SCR_VI bit isn't set
190	 *
191	 * Things go downhill at this point.
192	 *
193	 * Draw the new screen from scratch, and add a status line.
194	 */
195	F_SET(new,
196	    SC_SCR_REFORMAT | SC_STATUS |
197	    F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));
198	return (0);
199}
200
201/*
202 * vs_vsplit --
203 *	Create a new screen, vertically.
204 *
205 * PUBLIC: int vs_vsplit __P((SCR *, SCR *));
206 */
207int
208vs_vsplit(SCR *sp, SCR *new)
209{
210	GS *gp;
211	size_t cols;
212
213	gp = sp->gp;
214
215	/* Check to see if it's possible. */
216	if (sp->cols / 2 <= MINIMUM_SCREEN_COLS) {
217		msgq(sp, M_ERR,
218		    "288|Screen must be larger than %d columns to split",
219		    MINIMUM_SCREEN_COLS * 2);
220		return (1);
221	}
222
223	/* Wait for any messages in the screen. */
224	vs_resolve(sp, NULL, 1);
225
226	/* Get a new screen map. */
227	CALLOC(sp, _HMAP(new), SMAP *, SIZE_HMAP(sp), sizeof(SMAP));
228	if (_HMAP(new) == NULL)
229		return (1);
230	_HMAP(new)->lno = sp->lno;
231	_HMAP(new)->coff = 0;
232	_HMAP(new)->soff = 1;
233
234	/*
235	 * Split the screen in half; we have to sacrifice a column to delimit
236	 * the screens.
237	 *
238	 * XXX
239	 * We always split to the right... that makes more sense to me, and
240	 * I don't want to play the stupid games that I play when splitting
241	 * horizontally.
242	 *
243	 * XXX
244	 * We reserve a column for the screen, "knowing" that curses needs
245	 * one.  This should be worked out with the display interface.
246	 */
247	cols = sp->cols / 2;
248	new->cols = sp->cols - cols - 1;
249	sp->cols = cols;
250	new->coff = sp->coff + cols + 1;
251	sp->cno = 0;
252
253	/* Nothing else changes. */
254	new->rows = sp->rows;
255	new->t_rows = sp->t_rows;
256	new->t_maxrows = sp->t_maxrows;
257	new->t_minrows = sp->t_minrows;
258	new->roff = sp->roff;
259	new->defscroll = sp->defscroll;
260	_TMAP(new) = _HMAP(new) + (new->t_rows - 1);
261
262	/* Fit the screen into the logical chain. */
263	vs_insert(new, sp->gp);
264
265	/* Tell the display that we're splitting. */
266	(void)gp->scr_split(sp, new);
267
268	/* Redraw the old screen from scratch. */
269	F_SET(sp, SC_SCR_REFORMAT | SC_STATUS);
270
271	/*
272	 * Initialize the screen flags:
273	 *
274	 * If we're in vi mode in one screen, we don't have to reinitialize.
275	 * This isn't just a cosmetic fix.  The path goes like this:
276	 *
277	 *	return into vi(), SC_SSWITCH set
278	 *	call vs_refresh() with SC_STATUS set
279	 *	call vs_resolve to display the status message
280	 *	call vs_refresh() because the SC_SCR_VI bit isn't set
281	 *
282	 * Things go downhill at this point.
283	 *
284	 * Draw the new screen from scratch, and add a status line.
285	 */
286	F_SET(new,
287	    SC_SCR_REFORMAT | SC_STATUS |
288	    F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));
289	return (0);
290}
291
292/*
293 * vs_insert --
294 *	Insert the new screen into the correct place in the logical
295 *	chain.
296 */
297static void
298vs_insert(SCR *sp, GS *gp)
299{
300	SCR *tsp;
301
302	gp = sp->gp;
303
304	/* Move past all screens with lower row numbers. */
305	TAILQ_FOREACH(tsp, gp->dq, q)
306		if (tsp->roff >= sp->roff)
307			break;
308	/*
309	 * Move past all screens with the same row number and lower
310	 * column numbers.
311	 */
312	for (; tsp != NULL; tsp = TAILQ_NEXT(tsp, q))
313		if (tsp->roff != sp->roff || tsp->coff > sp->coff)
314			break;
315
316	/*
317	 * If we reached the end, this screen goes there.  Otherwise,
318	 * put it before or after the screen where we stopped.
319	 */
320	if (tsp == NULL) {
321		TAILQ_INSERT_TAIL(gp->dq, sp, q);
322	} else if (tsp->roff < sp->roff ||
323	    (tsp->roff == sp->roff && tsp->coff < sp->coff)) {
324		TAILQ_INSERT_AFTER(gp->dq, tsp, sp, q);
325	} else
326		TAILQ_INSERT_BEFORE(tsp, sp, q);
327}
328
329/*
330 * vs_discard --
331 *	Discard the screen, folding the real-estate into a related screen,
332 *	if one exists, and return that screen.
333 *
334 * PUBLIC: int vs_discard __P((SCR *, SCR **));
335 */
336int
337vs_discard(SCR *sp, SCR **spp)
338{
339	GS *gp;
340	SCR *tsp, **lp, *list[100];
341	jdir_t jdir;
342
343	gp = sp->gp;
344
345	/*
346	 * Save the old screen's cursor information.
347	 *
348	 * XXX
349	 * If called after file_end(), and the underlying file was a tmp
350	 * file, it may have gone away.
351	 */
352	if (sp->frp != NULL) {
353		sp->frp->lno = sp->lno;
354		sp->frp->cno = sp->cno;
355		F_SET(sp->frp, FR_CURSORSET);
356	}
357
358	/* If no other screens to join, we're done. */
359	if (!IS_SPLIT(sp)) {
360		(void)gp->scr_discard(sp, NULL);
361
362		if (spp != NULL)
363			*spp = NULL;
364		return (0);
365	}
366
367	/*
368	 * Find a set of screens that cover one of the screen's borders.
369	 * Check the vertical axis first, for no particular reason.
370	 *
371	 * XXX
372	 * It's possible (I think?), to create a screen that shares no full
373	 * border with any other set of screens, so we can't discard it.  We
374	 * just complain at the user until they clean it up.
375	 */
376	if (vs_join(sp, list, &jdir))
377		return (1);
378
379	/*
380	 * Modify the affected screens.  Redraw the modified screen(s) from
381	 * scratch, setting a status line.  If this is ever a performance
382	 * problem we could play games with the map, but I wrote that code
383	 * before and it was never clean or easy.
384	 *
385	 * Don't clean up the discarded screen's information.  If the screen
386	 * isn't exiting, we'll do the work when the user redisplays it.
387	 */
388	switch (jdir) {
389	case HORIZ_FOLLOW:
390	case HORIZ_PRECEDE:
391		for (lp = &list[0]; (tsp = *lp) != NULL; ++lp) {
392			/*
393			 * Small screens: see vs_refresh.c section 6a.  Adjust
394			 * text line info, unless it's a small screen.
395			 *
396			 * Reset the length of the default scroll.
397			 *
398			 * Reset the map references.
399			 */
400			tsp->rows += sp->rows;
401			if (!IS_SMALL(tsp))
402				tsp->t_rows = tsp->t_minrows = tsp->rows - 1;
403			tsp->t_maxrows = tsp->rows - 1;
404
405			tsp->defscroll = tsp->t_maxrows / 2;
406
407			*(_HMAP(tsp) + (tsp->t_rows - 1)) = *_TMAP(tsp);
408			_TMAP(tsp) = _HMAP(tsp) + (tsp->t_rows - 1);
409
410			switch (jdir) {
411			case HORIZ_FOLLOW:
412				tsp->roff = sp->roff;
413				vs_sm_fill(tsp, OOBLNO, P_TOP);
414				break;
415			case HORIZ_PRECEDE:
416				vs_sm_fill(tsp, OOBLNO, P_BOTTOM);
417				break;
418			default:
419				abort();
420			}
421			F_SET(tsp, SC_STATUS);
422		}
423		break;
424	case VERT_FOLLOW:
425	case VERT_PRECEDE:
426		for (lp = &list[0]; (tsp = *lp) != NULL; ++lp) {
427			if (jdir == VERT_FOLLOW)
428				tsp->coff = sp->coff;
429			tsp->cols += sp->cols + 1;	/* XXX: DIVIDER */
430			vs_sm_fill(tsp, OOBLNO, P_TOP);
431			F_SET(tsp, SC_STATUS);
432		}
433		break;
434	default:
435		abort();
436	}
437
438	/* Find the closest screen that changed and move to it. */
439	tsp = list[0];
440	if (spp != NULL)
441		*spp = tsp;
442
443	/* Tell the display that we're discarding a screen. */
444	(void)gp->scr_discard(sp, list);
445
446	return (0);
447}
448
449/*
450 * vs_join --
451 *	Find a set of screens that covers a screen's border.
452 */
453static int
454vs_join(SCR *sp, SCR **listp, jdir_t *jdirp)
455{
456	GS *gp;
457	SCR **lp, *tsp;
458	int first;
459	size_t tlen;
460
461	gp = sp->gp;
462
463	/* Check preceding vertical. */
464	for (lp = listp, tlen = sp->rows,
465	    tsp = TAILQ_FIRST(gp->dq);
466	    tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) {
467		if (sp == tsp)
468			continue;
469		/* Test if precedes the screen vertically. */
470		if (tsp->coff + tsp->cols + 1 != sp->coff)
471			continue;
472		/*
473		 * Test if a subset on the vertical axis.  If overlaps the
474		 * beginning or end, we can't join on this axis at all.
475		 */
476		if (tsp->roff > sp->roff + sp->rows)
477			continue;
478		if (tsp->roff < sp->roff) {
479			if (tsp->roff + tsp->rows >= sp->roff)
480				break;
481			continue;
482		}
483		if (tsp->roff + tsp->rows > sp->roff + sp->rows)
484			break;
485#ifdef DEBUG
486		if (tlen < tsp->rows)
487			abort();
488#endif
489		tlen -= tsp->rows;
490		*lp++ = tsp;
491	}
492	if (tlen == 0) {
493		*lp = NULL;
494		*jdirp = VERT_PRECEDE;
495		return (0);
496	}
497
498	/* Check following vertical. */
499	for (lp = listp, tlen = sp->rows,
500	    tsp = TAILQ_FIRST(gp->dq);
501	    tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) {
502		if (sp == tsp)
503			continue;
504		/* Test if follows the screen vertically. */
505		if (tsp->coff != sp->coff + sp->cols + 1)
506			continue;
507		/*
508		 * Test if a subset on the vertical axis.  If overlaps the
509		 * beginning or end, we can't join on this axis at all.
510		 */
511		if (tsp->roff > sp->roff + sp->rows)
512			continue;
513		if (tsp->roff < sp->roff) {
514			if (tsp->roff + tsp->rows >= sp->roff)
515				break;
516			continue;
517		}
518		if (tsp->roff + tsp->rows > sp->roff + sp->rows)
519			break;
520#ifdef DEBUG
521		if (tlen < tsp->rows)
522			abort();
523#endif
524		tlen -= tsp->rows;
525		*lp++ = tsp;
526	}
527	if (tlen == 0) {
528		*lp = NULL;
529		*jdirp = VERT_FOLLOW;
530		return (0);
531	}
532
533	/* Check preceding horizontal. */
534	for (first = 0, lp = listp, tlen = sp->cols,
535	    tsp = TAILQ_FIRST(gp->dq);
536	    tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) {
537		if (sp == tsp)
538			continue;
539		/* Test if precedes the screen horizontally. */
540		if (tsp->roff + tsp->rows != sp->roff)
541			continue;
542		/*
543		 * Test if a subset on the horizontal axis.  If overlaps the
544		 * beginning or end, we can't join on this axis at all.
545		 */
546		if (tsp->coff > sp->coff + sp->cols)
547			continue;
548		if (tsp->coff < sp->coff) {
549			if (tsp->coff + tsp->cols >= sp->coff)
550				break;
551			continue;
552		}
553		if (tsp->coff + tsp->cols > sp->coff + sp->cols)
554			break;
555#ifdef DEBUG
556		if (tlen < tsp->cols)
557			abort();
558#endif
559		tlen -= tsp->cols + first;
560		first = 1;
561		*lp++ = tsp;
562	}
563	if (tlen == 0) {
564		*lp = NULL;
565		*jdirp = HORIZ_PRECEDE;
566		return (0);
567	}
568
569	/* Check following horizontal. */
570	for (first = 0, lp = listp, tlen = sp->cols,
571	    tsp = TAILQ_FIRST(gp->dq);
572	    tsp != NULL; tsp = TAILQ_NEXT(tsp, q)) {
573		if (sp == tsp)
574			continue;
575		/* Test if precedes the screen horizontally. */
576		if (tsp->roff != sp->roff + sp->rows)
577			continue;
578		/*
579		 * Test if a subset on the horizontal axis.  If overlaps the
580		 * beginning or end, we can't join on this axis at all.
581		 */
582		if (tsp->coff > sp->coff + sp->cols)
583			continue;
584		if (tsp->coff < sp->coff) {
585			if (tsp->coff + tsp->cols >= sp->coff)
586				break;
587			continue;
588		}
589		if (tsp->coff + tsp->cols > sp->coff + sp->cols)
590			break;
591#ifdef DEBUG
592		if (tlen < tsp->cols)
593			abort();
594#endif
595		tlen -= tsp->cols + first;
596		first = 1;
597		*lp++ = tsp;
598	}
599	if (tlen == 0) {
600		*lp = NULL;
601		*jdirp = HORIZ_FOLLOW;
602		return (0);
603	}
604	return (1);
605}
606
607/*
608 * vs_fg --
609 *	Background the current screen, and foreground a new one.
610 *
611 * PUBLIC: int vs_fg __P((SCR *, SCR **, CHAR_T *, int));
612 */
613int
614vs_fg(SCR *sp, SCR **nspp, CHAR_T *name, int newscreen)
615{
616	GS *gp;
617	SCR *nsp;
618	char *np;
619	size_t nlen;
620
621	gp = sp->gp;
622
623	if (name)
624	    INT2CHAR(sp, name, STRLEN(name) + 1, np, nlen);
625	else
626	    np = NULL;
627	if (newscreen)
628		/* Get the specified background screen. */
629		nsp = vs_getbg(sp, np);
630	else
631		/* Swap screens. */
632		if (vs_swap(sp, &nsp, np))
633			return (1);
634
635	if ((*nspp = nsp) == NULL) {
636		msgq_wstr(sp, M_ERR, name,
637		    name == NULL ?
638		    "223|There are no background screens" :
639		    "224|There's no background screen editing a file named %s");
640		return (1);
641	}
642
643	if (newscreen) {
644		/* Remove the new screen from the background queue. */
645		TAILQ_REMOVE(gp->hq, nsp, q);
646
647		/* Split the screen; if we fail, hook the screen back in. */
648		if (vs_split(sp, nsp, 0)) {
649			TAILQ_INSERT_TAIL(gp->hq, nsp, q);
650			return (1);
651		}
652	} else {
653		/* Move the old screen to the background queue. */
654		TAILQ_REMOVE(gp->dq, sp, q);
655		TAILQ_INSERT_TAIL(gp->hq, sp, q);
656	}
657	return (0);
658}
659
660/*
661 * vs_bg --
662 *	Background the screen, and switch to the next one.
663 *
664 * PUBLIC: int vs_bg __P((SCR *));
665 */
666int
667vs_bg(SCR *sp)
668{
669	GS *gp;
670	SCR *nsp;
671
672	gp = sp->gp;
673
674	/* Try and join with another screen. */
675	if (vs_discard(sp, &nsp))
676		return (1);
677	if (nsp == NULL) {
678		msgq(sp, M_ERR,
679		    "225|You may not background your only displayed screen");
680		return (1);
681	}
682
683	/* Move the old screen to the background queue. */
684	TAILQ_REMOVE(gp->dq, sp, q);
685	TAILQ_INSERT_TAIL(gp->hq, sp, q);
686
687	/* Toss the screen map. */
688	free(_HMAP(sp));
689	_HMAP(sp) = NULL;
690
691	/* Switch screens. */
692	sp->nextdisp = nsp;
693	F_SET(sp, SC_SSWITCH);
694
695	return (0);
696}
697
698/*
699 * vs_swap --
700 *	Swap the current screen with a backgrounded one.
701 *
702 * PUBLIC: int vs_swap __P((SCR *, SCR **, char *));
703 */
704int
705vs_swap(SCR *sp, SCR **nspp, char *name)
706{
707	GS *gp;
708	SCR *nsp, *list[2];
709
710	gp = sp->gp;
711
712	/* Get the specified background screen. */
713	if ((*nspp = nsp = vs_getbg(sp, name)) == NULL)
714		return (0);
715
716	/*
717	 * Save the old screen's cursor information.
718	 *
719	 * XXX
720	 * If called after file_end(), and the underlying file was a tmp
721	 * file, it may have gone away.
722	 */
723	if (sp->frp != NULL) {
724		sp->frp->lno = sp->lno;
725		sp->frp->cno = sp->cno;
726		F_SET(sp->frp, FR_CURSORSET);
727	}
728
729	/* Switch screens. */
730	sp->nextdisp = nsp;
731	F_SET(sp, SC_SSWITCH);
732
733	/* Initialize terminal information. */
734	VIP(nsp)->srows = VIP(sp)->srows;
735
736	/* Initialize screen information. */
737	nsp->cols = sp->cols;
738	nsp->rows = sp->rows;	/* XXX: Only place in vi that sets rows. */
739	nsp->roff = sp->roff;
740
741	/*
742	 * Small screens: see vs_refresh.c, section 6a.
743	 *
744	 * The new screens may have different screen options sizes than the
745	 * old one, so use them.  Make sure that text counts aren't larger
746	 * than the new screen sizes.
747	 */
748	if (IS_SMALL(nsp)) {
749		nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW);
750		if (nsp->t_rows > sp->t_maxrows)
751			nsp->t_rows = nsp->t_maxrows;
752		if (nsp->t_minrows > sp->t_maxrows)
753			nsp->t_minrows = nsp->t_maxrows;
754	} else
755		nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1;
756
757	/* Reset the length of the default scroll. */
758	nsp->defscroll = nsp->t_maxrows / 2;
759
760	/* Allocate a new screen map. */
761	CALLOC_RET(nsp, _HMAP(nsp), SMAP *, SIZE_HMAP(nsp), sizeof(SMAP));
762	_TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1);
763
764	/* Fill the map. */
765	nsp->gp = sp->gp;
766	if (vs_sm_fill(nsp, nsp->lno, P_FILL))
767		return (1);
768
769	/*
770	 * The new screen replaces the old screen in the parent/child list.
771	 * We insert the new screen after the old one.  If we're exiting,
772	 * the exit will delete the old one, if we're foregrounding, the fg
773	 * code will move the old one to the background queue.
774	 */
775	TAILQ_REMOVE(gp->hq, nsp, q);
776	TAILQ_INSERT_AFTER(gp->dq, sp, nsp, q);
777
778	/*
779	 * Don't change the screen's cursor information other than to
780	 * note that the cursor is wrong.
781	 */
782	F_SET(VIP(nsp), VIP_CUR_INVALID);
783
784	/* Draw the new screen from scratch, and add a status line. */
785	F_SET(nsp, SC_SCR_REDRAW | SC_STATUS);
786
787	list[0] = nsp; list[1] = NULL;
788	(void)gp->scr_discard(sp, list);
789
790	return (0);
791}
792
793/*
794 * vs_resize --
795 *	Change the absolute size of the current screen.
796 *
797 * PUBLIC: int vs_resize __P((SCR *, long, adj_t));
798 */
799int
800vs_resize(SCR *sp, long int count, adj_t adj)
801{
802	GS *gp;
803	SCR *g, *s, *prev, *next, *list[3] = {NULL, NULL, NULL};
804	size_t g_off, s_off;
805
806	gp = sp->gp;
807
808	/*
809	 * Figure out which screens will grow, which will shrink, and
810	 * make sure it's possible.
811	 */
812	if (count == 0)
813		return (0);
814	if (adj == A_SET) {
815		if (sp->t_maxrows == count)
816			return (0);
817		if (sp->t_maxrows > count) {
818			adj = A_DECREASE;
819			count = sp->t_maxrows - count;
820		} else {
821			adj = A_INCREASE;
822			count = count - sp->t_maxrows;
823		}
824	}
825
826	/* Find first overlapping screen */
827	for (next = TAILQ_NEXT(sp, q); next != NULL &&
828	     (next->coff >= sp->coff + sp->cols ||
829	      next->coff + next->cols <= sp->coff);
830	     next = TAILQ_NEXT(next, q));
831	/* See if we can use it */
832	if (next != NULL &&
833	    (sp->coff != next->coff || sp->cols != next->cols))
834		next = NULL;
835	for (prev = TAILQ_PREV(sp, _dqh, q); prev != NULL &&
836	     (prev->coff >= sp->coff + sp->cols ||
837	      prev->coff + prev->cols <= sp->coff);
838	     prev = TAILQ_PREV(prev, _dqh, q));
839	if (prev != NULL &&
840	    (sp->coff != prev->coff || sp->cols != prev->cols))
841		prev = NULL;
842
843	g_off = s_off = 0;
844	if (adj == A_DECREASE) {
845		if (count < 0)
846			count = -count;
847		s = sp;
848		if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
849			goto toosmall;
850		if ((g = prev) == NULL) {
851			if ((g = next) == NULL)
852				goto toobig;
853			g_off = -count;
854		} else
855			s_off = count;
856	} else {
857		g = sp;
858		if ((s = next) != NULL &&
859		    s->t_maxrows >= MINIMUM_SCREEN_ROWS + count)
860				s_off = count;
861		else
862			s = NULL;
863		if (s == NULL) {
864			if ((s = prev) == NULL) {
865toobig:				msgq(sp, M_BERR, adj == A_DECREASE ?
866				    "227|The screen cannot shrink" :
867				    "228|The screen cannot grow");
868				return (1);
869			}
870			if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) {
871toosmall:			msgq(sp, M_BERR,
872				    "226|The screen can only shrink to %d rows",
873				    MINIMUM_SCREEN_ROWS);
874				return (1);
875			}
876			g_off = -count;
877		}
878	}
879
880	/*
881	 * Fix up the screens; we could optimize the reformatting of the
882	 * screen, but this isn't likely to be a common enough operation
883	 * to make it worthwhile.
884	 */
885	s->rows += -count;
886	s->roff += s_off;
887	g->rows += count;
888	g->roff += g_off;
889
890	g->t_rows += count;
891	if (g->t_minrows == g->t_maxrows)
892		g->t_minrows += count;
893	g->t_maxrows += count;
894	_TMAP(g) += count;
895	F_SET(g, SC_SCR_REFORMAT | SC_STATUS);
896
897	s->t_rows -= count;
898	s->t_maxrows -= count;
899	if (s->t_minrows > s->t_maxrows)
900		s->t_minrows = s->t_maxrows;
901	_TMAP(s) -= count;
902	F_SET(s, SC_SCR_REFORMAT | SC_STATUS);
903
904	/* XXXX */
905	list[0] = g; list[1] = s;
906	gp->scr_discard(0, list);
907
908	return (0);
909}
910
911/*
912 * vs_getbg --
913 *	Get the specified background screen, or, if name is NULL, the first
914 *	background screen.
915 */
916static SCR *
917vs_getbg(SCR *sp, char *name)
918{
919	GS *gp;
920	SCR *nsp;
921	char *p;
922
923	gp = sp->gp;
924
925	/* If name is NULL, return the first background screen on the list. */
926	if (name == NULL)
927		return (TAILQ_FIRST(gp->hq));
928
929	/* Search for a full match. */
930	TAILQ_FOREACH(nsp, gp->hq, q)
931		if (!strcmp(nsp->frp->name, name))
932			break;
933	if (nsp != NULL)
934		return (nsp);
935
936	/* Search for a last-component match. */
937	TAILQ_FOREACH(nsp, gp->hq, q) {
938		if ((p = strrchr(nsp->frp->name, '/')) == NULL)
939			p = nsp->frp->name;
940		else
941			++p;
942		if (!strcmp(p, name))
943			break;
944	}
945	if (nsp != NULL)
946		return (nsp);
947
948	return (NULL);
949}
950