xref: /minix/external/bsd/nvi/dist/vi/vs_split.c (revision 0a6a1f1d)
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
vs_split(SCR * sp,SCR * new,int ccl)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
vs_vsplit(SCR * sp,SCR * new)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
vs_insert(SCR * sp,WIN * wp)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
vs_discard(SCR * sp,SCR ** spp)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
vs_join(SCR * sp,SCR ** listp,jdir_t * jdirp)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
vs_fg(SCR * sp,SCR ** nspp,CHAR_T * name,int newscreen)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
vs_bg(SCR * sp)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
vs_swap(SCR * sp,SCR ** nspp,const char * name)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
vs_resize(SCR * sp,long int count,adj_t adj)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 *
vs_getbg(SCR * sp,const char * name)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