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