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