xref: /openbsd/usr.bin/vi/vi/vs_split.c (revision 721c3ea3)
1 /*	$OpenBSD: vs_split.c,v 1.16 2016/05/27 09:18:12 martijn Exp $	*/
2 
3 /*-
4  * Copyright (c) 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  * Copyright (c) 1993, 1994, 1995, 1996
7  *	Keith Bostic.  All rights reserved.
8  *
9  * See the LICENSE file for redistribution information.
10  */
11 
12 #include "config.h"
13 
14 #include <sys/types.h>
15 #include <sys/queue.h>
16 #include <sys/time.h>
17 
18 #include <bitstring.h>
19 #include <errno.h>
20 #include <limits.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "../common/common.h"
26 #include "vi.h"
27 
28 static SCR *vs_getbg(SCR *, char *);
29 
30 /*
31  * vs_split --
32  *	Create a new screen.
33  *
34  * PUBLIC: int vs_split(SCR *, SCR *, int);
35  */
36 int
vs_split(SCR * sp,SCR * new,int ccl)37 vs_split(SCR *sp, SCR *new, int ccl)
38 {
39 	GS *gp;
40 	SMAP *smp;
41 	size_t half;
42 	int issmallscreen, splitup;
43 
44 	gp = sp->gp;
45 
46 	/* Check to see if it's possible. */
47 	/* XXX: The IS_ONELINE fix will change this, too. */
48 	if (sp->rows < 4) {
49 		msgq(sp, M_ERR,
50 		    "Screen must be larger than %d lines to split", 4 - 1);
51 		return (1);
52 	}
53 
54 	/* Wait for any messages in the screen. */
55 	vs_resolve(sp, NULL, 1);
56 
57 	half = sp->rows / 2;
58 	if (ccl && half > 6)
59 		half = 6;
60 
61 	/* Get a new screen map. */
62 	CALLOC(sp, _HMAP(new), SIZE_HMAP(sp), sizeof(SMAP));
63 	if (_HMAP(new) == NULL)
64 		return (1);
65 	_HMAP(new)->lno = sp->lno;
66 	_HMAP(new)->coff = 0;
67 	_HMAP(new)->soff = 1;
68 
69 	/*
70 	 * Small screens: see vs_refresh.c section 6a.  Set a flag so
71 	 * we know to fix the screen up later.
72 	 */
73 	issmallscreen = IS_SMALL(sp);
74 
75 	/* The columns in the screen don't change. */
76 	new->cols = sp->cols;
77 
78 	/*
79 	 * Split the screen, and link the screens together.  If creating a
80 	 * screen to edit the colon command line or the cursor is in the top
81 	 * half of the current screen, the new screen goes under the current
82 	 * screen.  Else, it goes above the current screen.
83 	 *
84 	 * Recalculate current cursor position based on sp->lno, we're called
85 	 * with the cursor on the colon command line.  Then split the screen
86 	 * in half and update the shared information.
87 	 */
88 	splitup =
89 	    !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half;
90 	if (splitup) {				/* Old is bottom half. */
91 		new->rows = sp->rows - half;	/* New. */
92 		new->woff = sp->woff;
93 		sp->rows = half;		/* Old. */
94 		sp->woff += new->rows;
95 						/* Link in before old. */
96 		TAILQ_INSERT_BEFORE(sp, new, q);
97 
98 		/*
99 		 * If the parent is the bottom half of the screen, shift
100 		 * the map down to match on-screen text.
101 		 */
102 		memmove(_HMAP(sp), _HMAP(sp) + new->rows,
103 		    (sp->t_maxrows - new->rows) * sizeof(SMAP));
104 	} else {				/* Old is top half. */
105 		new->rows = half;		/* New. */
106 		sp->rows -= half;		/* Old. */
107 		new->woff = sp->woff + sp->rows;
108 						/* Link in after old. */
109 		TAILQ_INSERT_AFTER(&gp->dq, sp, new, q);
110 	}
111 
112 	/* Adjust maximum text count. */
113 	sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
114 	new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1;
115 
116 	/*
117 	 * Small screens: see vs_refresh.c, section 6a.
118 	 *
119 	 * The child may have different screen options sizes than the parent,
120 	 * so use them.  Guarantee that text counts aren't larger than the
121 	 * new screen sizes.
122 	 */
123 	if (issmallscreen) {
124 		/* Fix the text line count for the parent. */
125 		if (splitup)
126 			sp->t_rows -= new->rows;
127 
128 		/* Fix the parent screen. */
129 		if (sp->t_rows > sp->t_maxrows)
130 			sp->t_rows = sp->t_maxrows;
131 		if (sp->t_minrows > sp->t_maxrows)
132 			sp->t_minrows = sp->t_maxrows;
133 
134 		/* Fix the child screen. */
135 		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
136 		if (new->t_rows > new->t_maxrows)
137 			new->t_rows = new->t_maxrows;
138 		if (new->t_minrows > new->t_maxrows)
139 			new->t_minrows = new->t_maxrows;
140 	} else {
141 		sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1;
142 
143 		/*
144 		 * The new screen may be a small screen, even if the parent
145 		 * was not.  Don't complain if O_WINDOW is too large, we're
146 		 * splitting the screen so the screen is much smaller than
147 		 * normal.
148 		 */
149 		new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW);
150 		if (new->t_rows > new->rows - 1)
151 			new->t_minrows = new->t_rows =
152 			    IS_ONELINE(new) ? 1 : new->rows - 1;
153 	}
154 
155 	/* Adjust the ends of the new and old maps. */
156 	_TMAP(sp) = IS_ONELINE(sp) ?
157 	    _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1);
158 	_TMAP(new) = IS_ONELINE(new) ?
159 	    _HMAP(new) : _HMAP(new) + (new->t_rows - 1);
160 
161 	/* Reset the length of the default scroll. */
162 	if ((sp->defscroll = sp->t_maxrows / 2) == 0)
163 		sp->defscroll = 1;
164 	if ((new->defscroll = new->t_maxrows / 2) == 0)
165 		new->defscroll = 1;
166 
167 	/*
168 	 * Initialize the screen flags:
169 	 *
170 	 * If we're in vi mode in one screen, we don't have to reinitialize.
171 	 * This isn't just a cosmetic fix.  The path goes like this:
172 	 *
173 	 *	return into vi(), SC_SSWITCH set
174 	 *	call vs_refresh() with SC_STATUS set
175 	 *	call vs_resolve to display the status message
176 	 *	call vs_refresh() because the SC_SCR_VI bit isn't set
177 	 *
178 	 * Things go downhill at this point.
179 	 *
180 	 * Draw the new screen from scratch, and add a status line.
181 	 */
182 	F_SET(new,
183 	    SC_SCR_REFORMAT | SC_STATUS |
184 	    F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX));
185 	return (0);
186 }
187 
188 /*
189  * vs_discard --
190  *	Discard the screen, folding the real-estate into a related screen,
191  *	if one exists, and return that screen.
192  *
193  * PUBLIC: int vs_discard(SCR *, SCR **);
194  */
195 int
vs_discard(SCR * sp,SCR ** spp)196 vs_discard(SCR *sp, SCR **spp)
197 {
198 	SCR *nsp;
199 	dir_t dir;
200 
201 	/*
202 	 * Save the old screen's cursor information.
203 	 *
204 	 * XXX
205 	 * If called after file_end(), and the underlying file was a tmp
206 	 * file, it may have gone away.
207 	 */
208 	if (sp->frp != NULL) {
209 		sp->frp->lno = sp->lno;
210 		sp->frp->cno = sp->cno;
211 		F_SET(sp->frp, FR_CURSORSET);
212 	}
213 
214 	/*
215 	 * Add into a previous screen and then into a subsequent screen, as
216 	 * they're the closest to the current screen.  If that doesn't work,
217 	 * there was no screen to join.
218 	 */
219 	if ((nsp = TAILQ_PREV(sp, _dqh, q))) {
220 		nsp->rows += sp->rows;
221 		sp = nsp;
222 		dir = FORWARD;
223 	} else if ((nsp = TAILQ_NEXT(sp, q))) {
224 		nsp->woff = sp->woff;
225 		nsp->rows += sp->rows;
226 		sp = nsp;
227 		dir = BACKWARD;
228 	} else {
229 		sp = NULL;
230 		dir = 0;	/* unused */
231 	}
232 
233 	if (spp != NULL)
234 		*spp = sp;
235 	if (sp == NULL)
236 		return (0);
237 
238 	/*
239 	 * Make no effort to clean up the discarded screen's information.  If
240 	 * it's not exiting, we'll do the work when the user redisplays it.
241 	 *
242 	 * Small screens: see vs_refresh.c section 6a.  Adjust text line info,
243 	 * unless it's a small screen.
244 	 *
245 	 * Reset the length of the default scroll.
246 	 */
247 	if (!IS_SMALL(sp))
248 		sp->t_rows = sp->t_minrows = sp->rows - 1;
249 	sp->t_maxrows = sp->rows - 1;
250 	sp->defscroll = sp->t_maxrows / 2;
251 	*(HMAP + (sp->t_rows - 1)) = *TMAP;
252 	TMAP = HMAP + (sp->t_rows - 1);
253 
254 	/*
255 	 * Draw the new screen from scratch, and add a status line.
256 	 *
257 	 * XXX
258 	 * We could play games with the map, if this were ever to be a
259 	 * performance problem, but I wrote the code a few times and it
260 	 * was never clean or easy.
261 	 */
262 	switch (dir) {
263 	case FORWARD:
264 		vs_sm_fill(sp, OOBLNO, P_TOP);
265 		break;
266 	case BACKWARD:
267 		vs_sm_fill(sp, OOBLNO, P_BOTTOM);
268 		break;
269 	default:
270 		abort();
271 	}
272 
273 	F_SET(sp, SC_STATUS);
274 	return (0);
275 }
276 
277 /*
278  * vs_fg --
279  *	Background the current screen, and foreground a new one.
280  *
281  * PUBLIC: int vs_fg(SCR *, SCR **, CHAR_T *, int);
282  */
283 int
vs_fg(SCR * sp,SCR ** nspp,CHAR_T * name,int newscreen)284 vs_fg(SCR *sp, SCR **nspp, CHAR_T *name, int newscreen)
285 {
286 	GS *gp;
287 	SCR *nsp;
288 
289 	gp = sp->gp;
290 
291 	if (newscreen)
292 		/* Get the specified background screen. */
293 		nsp = vs_getbg(sp, name);
294 	else
295 		/* Swap screens. */
296 		if (vs_swap(sp, &nsp, name))
297 			return (1);
298 
299 	if ((*nspp = nsp) == NULL) {
300 		msgq_str(sp, M_ERR, name,
301 		    name == NULL ?
302 		    "There are no background screens" :
303 		    "There's no background screen editing a file named %s");
304 		return (1);
305 	}
306 
307 	if (newscreen) {
308 		/* Remove the new screen from the background queue. */
309 		TAILQ_REMOVE(&gp->hq, nsp, q);
310 
311 		/* Split the screen; if we fail, hook the screen back in. */
312 		if (vs_split(sp, nsp, 0)) {
313 			TAILQ_INSERT_TAIL(&gp->hq, nsp, q);
314 			return (1);
315 		}
316 	} else {
317 		/* Move the old screen to the background queue. */
318 		TAILQ_REMOVE(&gp->dq, sp, q);
319 		TAILQ_INSERT_TAIL(&gp->hq, sp, q);
320 	}
321 	return (0);
322 }
323 
324 /*
325  * vs_bg --
326  *	Background the screen, and switch to the next one.
327  *
328  * PUBLIC: int vs_bg(SCR *);
329  */
330 int
vs_bg(SCR * sp)331 vs_bg(SCR *sp)
332 {
333 	GS *gp;
334 	SCR *nsp;
335 
336 	gp = sp->gp;
337 
338 	/* Try and join with another screen. */
339 	if (vs_discard(sp, &nsp))
340 		return (1);
341 	if (nsp == NULL) {
342 		msgq(sp, M_ERR,
343 		    "You may not background your only displayed screen");
344 		return (1);
345 	}
346 
347 	/* Move the old screen to the background queue. */
348 	TAILQ_REMOVE(&gp->dq, sp, q);
349 	TAILQ_INSERT_TAIL(&gp->hq, sp, q);
350 
351 	/* Toss the screen map. */
352 	free(_HMAP(sp));
353 	_HMAP(sp) = NULL;
354 
355 	/* Switch screens. */
356 	sp->nextdisp = nsp;
357 	F_SET(sp, SC_SSWITCH);
358 
359 	return (0);
360 }
361 
362 /*
363  * vs_swap --
364  *	Swap the current screen with a backgrounded one.
365  *
366  * PUBLIC: int vs_swap(SCR *, SCR **, char *);
367  */
368 int
vs_swap(SCR * sp,SCR ** nspp,char * name)369 vs_swap(SCR *sp, SCR **nspp, char *name)
370 {
371 	GS *gp;
372 	SCR *nsp;
373 
374 	gp = sp->gp;
375 
376 	/* Get the specified background screen. */
377 	if ((*nspp = nsp = vs_getbg(sp, name)) == NULL)
378 		return (0);
379 
380 	/*
381 	 * Save the old screen's cursor information.
382 	 *
383 	 * XXX
384 	 * If called after file_end(), and the underlying file was a tmp
385 	 * file, it may have gone away.
386 	 */
387 	if (sp->frp != NULL) {
388 		sp->frp->lno = sp->lno;
389 		sp->frp->cno = sp->cno;
390 		F_SET(sp->frp, FR_CURSORSET);
391 	}
392 
393 	/* Switch screens. */
394 	sp->nextdisp = nsp;
395 	F_SET(sp, SC_SSWITCH);
396 
397 	/* Initialize terminal information. */
398 	VIP(nsp)->srows = VIP(sp)->srows;
399 
400 	/* Initialize screen information. */
401 	nsp->cols = sp->cols;
402 	nsp->rows = sp->rows;	/* XXX: Only place in vi that sets rows. */
403 	nsp->woff = sp->woff;
404 
405 	/*
406 	 * Small screens: see vs_refresh.c, section 6a.
407 	 *
408 	 * The new screens may have different screen options sizes than the
409 	 * old one, so use them.  Make sure that text counts aren't larger
410 	 * than the new screen sizes.
411 	 */
412 	if (IS_SMALL(nsp)) {
413 		nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW);
414 		if (nsp->t_rows > sp->t_maxrows)
415 			nsp->t_rows = nsp->t_maxrows;
416 		if (nsp->t_minrows > sp->t_maxrows)
417 			nsp->t_minrows = nsp->t_maxrows;
418 	} else
419 		nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1;
420 
421 	/* Reset the length of the default scroll. */
422 	nsp->defscroll = nsp->t_maxrows / 2;
423 
424 	/* Allocate a new screen map. */
425 	CALLOC_RET(nsp, _HMAP(nsp), SIZE_HMAP(nsp), sizeof(SMAP));
426 	_TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1);
427 
428 	/* Fill the map. */
429 	if (vs_sm_fill(nsp, nsp->lno, P_FILL))
430 		return (1);
431 
432 	/*
433 	 * The new screen replaces the old screen in the parent/child list.
434 	 * We insert the new screen after the old one.  If we're exiting,
435 	 * the exit will delete the old one, if we're foregrounding, the fg
436 	 * code will move the old one to the background queue.
437 	 */
438 	TAILQ_REMOVE(&gp->hq, nsp, q);
439 	TAILQ_INSERT_AFTER(&gp->dq, sp, nsp, q);
440 
441 	/*
442 	 * Don't change the screen's cursor information other than to
443 	 * note that the cursor is wrong.
444 	 */
445 	F_SET(VIP(nsp), VIP_CUR_INVALID);
446 
447 	/* Draw the new screen from scratch, and add a status line. */
448 	F_SET(nsp, SC_SCR_REDRAW | SC_STATUS);
449 	return (0);
450 }
451 
452 /*
453  * vs_resize --
454  *	Change the absolute size of the current screen.
455  *
456  * PUBLIC: int vs_resize(SCR *, long, adj_t);
457  */
458 int
vs_resize(SCR * sp,long count,adj_t adj)459 vs_resize(SCR *sp, long count, adj_t adj)
460 {
461 	GS *gp;
462 	SCR *g, *s;
463 	size_t g_off, s_off;
464 
465 	gp = sp->gp;
466 
467 	/*
468 	 * Figure out which screens will grow, which will shrink, and
469 	 * make sure it's possible.
470 	 */
471 	if (count == 0)
472 		return (0);
473 	if (adj == A_SET) {
474 		if (sp->t_maxrows == count)
475 			return (0);
476 		if (sp->t_maxrows > count) {
477 			adj = A_DECREASE;
478 			count = sp->t_maxrows - count;
479 		} else {
480 			adj = A_INCREASE;
481 			count = count - sp->t_maxrows;
482 		}
483 	}
484 
485 	g_off = s_off = 0;
486 	if (adj == A_DECREASE) {
487 		if (count < 0)
488 			count = -count;
489 		s = sp;
490 		if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
491 			goto toosmall;
492 		if ((g = TAILQ_PREV(sp, _dqh, q)) == NULL) {
493 			if ((g = TAILQ_NEXT(sp, q)) == NULL)
494 				goto toobig;
495 			g_off = -count;
496 		} else
497 			s_off = count;
498 	} else {
499 		g = sp;
500 		if ((s = TAILQ_NEXT(sp, q)))
501 			if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count)
502 				s = NULL;
503 			else
504 				s_off = count;
505 		else
506 			s = NULL;
507 		if (s == NULL) {
508 			if ((s = TAILQ_PREV(sp, _dqh, q)) == NULL) {
509 toobig:				msgq(sp, M_BERR, adj == A_DECREASE ?
510 				    "The screen cannot shrink" :
511 				    "The screen cannot grow");
512 				return (1);
513 			}
514 			if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) {
515 toosmall:			msgq(sp, M_BERR,
516 				    "The screen can only shrink to %d rows",
517 				    MINIMUM_SCREEN_ROWS);
518 				return (1);
519 			}
520 			g_off = -count;
521 		}
522 	}
523 
524 	/*
525 	 * Fix up the screens; we could optimize the reformatting of the
526 	 * screen, but this isn't likely to be a common enough operation
527 	 * to make it worthwhile.
528 	 */
529 	s->rows += -count;
530 	s->woff += s_off;
531 	g->rows += count;
532 	g->woff += g_off;
533 
534 	g->t_rows += count;
535 	if (g->t_minrows == g->t_maxrows)
536 		g->t_minrows += count;
537 	g->t_maxrows += count;
538 	_TMAP(g) += count;
539 	F_SET(g, SC_SCR_REFORMAT | SC_STATUS);
540 
541 	s->t_rows -= count;
542 	s->t_maxrows -= count;
543 	if (s->t_minrows > s->t_maxrows)
544 		s->t_minrows = s->t_maxrows;
545 	_TMAP(s) -= count;
546 	F_SET(s, SC_SCR_REFORMAT | SC_STATUS);
547 
548 	return (0);
549 }
550 
551 /*
552  * vs_getbg --
553  *	Get the specified background screen, or, if name is NULL, the first
554  *	background screen.
555  */
556 static SCR *
vs_getbg(SCR * sp,char * name)557 vs_getbg(SCR *sp, char *name)
558 {
559 	GS *gp;
560 	SCR *nsp;
561 	char *p;
562 
563 	gp = sp->gp;
564 
565 	/* If name is NULL, return the first background screen on the list. */
566 	if (name == NULL)
567 		return (TAILQ_FIRST(&gp->hq));
568 
569 	/* Search for a full match. */
570 	TAILQ_FOREACH(nsp, &gp->hq, q) {
571 		if (!strcmp(nsp->frp->name, name))
572 			return(nsp);
573 	}
574 
575 	/* Search for a last-component match. */
576 	TAILQ_FOREACH(nsp, &gp->hq, q) {
577 		if ((p = strrchr(nsp->frp->name, '/')) == NULL)
578 			p = nsp->frp->name;
579 		else
580 			++p;
581 		if (!strcmp(p, name))
582 			return(nsp);
583 	}
584 
585 	return (NULL);
586 }
587