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