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