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 } 608