1 /* $OpenBSD: screen.c,v 1.58 2020/02/05 13:06:49 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <vis.h> 25 26 #include "tmux.h" 27 28 /* Selected area in screen. */ 29 struct screen_sel { 30 int hidden; 31 int rectangle; 32 int modekeys; 33 34 u_int sx; 35 u_int sy; 36 37 u_int ex; 38 u_int ey; 39 40 struct grid_cell cell; 41 }; 42 43 /* Entry on title stack. */ 44 struct screen_title_entry { 45 char *text; 46 47 TAILQ_ENTRY(screen_title_entry) entry; 48 }; 49 TAILQ_HEAD(screen_titles, screen_title_entry); 50 51 static void screen_resize_y(struct screen *, u_int); 52 53 static void screen_reflow(struct screen *, u_int); 54 55 /* Free titles stack. */ 56 static void 57 screen_free_titles(struct screen *s) 58 { 59 struct screen_title_entry *title_entry; 60 61 if (s->titles == NULL) 62 return; 63 64 while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) { 65 TAILQ_REMOVE(s->titles, title_entry, entry); 66 free(title_entry->text); 67 free(title_entry); 68 } 69 70 free(s->titles); 71 s->titles = NULL; 72 } 73 74 /* Create a new screen. */ 75 void 76 screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) 77 { 78 s->grid = grid_create(sx, sy, hlimit); 79 s->title = xstrdup(""); 80 s->titles = NULL; 81 82 s->cstyle = 0; 83 s->ccolour = xstrdup(""); 84 s->tabs = NULL; 85 s->sel = NULL; 86 87 screen_reinit(s); 88 } 89 90 /* Reinitialise screen. */ 91 void 92 screen_reinit(struct screen *s) 93 { 94 s->cx = 0; 95 s->cy = 0; 96 97 s->rupper = 0; 98 s->rlower = screen_size_y(s) - 1; 99 100 s->mode = MODE_CURSOR | MODE_WRAP; 101 102 screen_reset_tabs(s); 103 104 grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); 105 106 screen_clear_selection(s); 107 screen_free_titles(s); 108 } 109 110 /* Destroy a screen. */ 111 void 112 screen_free(struct screen *s) 113 { 114 free(s->sel); 115 free(s->tabs); 116 free(s->title); 117 free(s->ccolour); 118 119 grid_destroy(s->grid); 120 121 screen_free_titles(s); 122 } 123 124 /* Reset tabs to default, eight spaces apart. */ 125 void 126 screen_reset_tabs(struct screen *s) 127 { 128 u_int i; 129 130 free(s->tabs); 131 132 if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL) 133 fatal("bit_alloc failed"); 134 for (i = 8; i < screen_size_x(s); i += 8) 135 bit_set(s->tabs, i); 136 } 137 138 /* Set screen cursor style. */ 139 void 140 screen_set_cursor_style(struct screen *s, u_int style) 141 { 142 if (style <= 6) 143 s->cstyle = style; 144 } 145 146 /* Set screen cursor colour. */ 147 void 148 screen_set_cursor_colour(struct screen *s, const char *colour) 149 { 150 free(s->ccolour); 151 s->ccolour = xstrdup(colour); 152 } 153 154 /* Set screen title. */ 155 int 156 screen_set_title(struct screen *s, const char *title) 157 { 158 if (!utf8_isvalid(title)) 159 return (0); 160 free(s->title); 161 s->title = xstrdup(title); 162 return (1); 163 } 164 165 /* Set screen path. */ 166 void 167 screen_set_path(struct screen *s, const char *path) 168 { 169 free(s->path); 170 utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); 171 } 172 173 /* Push the current title onto the stack. */ 174 void 175 screen_push_title(struct screen *s) 176 { 177 struct screen_title_entry *title_entry; 178 179 if (s->titles == NULL) { 180 s->titles = xmalloc(sizeof *s->titles); 181 TAILQ_INIT(s->titles); 182 } 183 title_entry = xmalloc(sizeof *title_entry); 184 title_entry->text = xstrdup(s->title); 185 TAILQ_INSERT_HEAD(s->titles, title_entry, entry); 186 } 187 188 /* 189 * Pop a title from the stack and set it as the screen title. If the stack is 190 * empty, do nothing. 191 */ 192 void 193 screen_pop_title(struct screen *s) 194 { 195 struct screen_title_entry *title_entry; 196 197 if (s->titles == NULL) 198 return; 199 200 title_entry = TAILQ_FIRST(s->titles); 201 if (title_entry != NULL) { 202 screen_set_title(s, title_entry->text); 203 204 TAILQ_REMOVE(s->titles, title_entry, entry); 205 free(title_entry->text); 206 free(title_entry); 207 } 208 } 209 210 /* Resize screen. */ 211 void 212 screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) 213 { 214 if (sx < 1) 215 sx = 1; 216 if (sy < 1) 217 sy = 1; 218 219 if (sx != screen_size_x(s)) { 220 s->grid->sx = sx; 221 screen_reset_tabs(s); 222 } else 223 reflow = 0; 224 225 if (sy != screen_size_y(s)) 226 screen_resize_y(s, sy); 227 228 if (reflow) 229 screen_reflow(s, sx); 230 } 231 232 static void 233 screen_resize_y(struct screen *s, u_int sy) 234 { 235 struct grid *gd = s->grid; 236 u_int needed, available, oldy, i; 237 238 if (sy == 0) 239 fatalx("zero size"); 240 oldy = screen_size_y(s); 241 242 /* 243 * When resizing: 244 * 245 * If the height is decreasing, delete lines from the bottom until 246 * hitting the cursor, then push lines from the top into the history. 247 * 248 * When increasing, pull as many lines as possible from scrolled 249 * history (not explicitly cleared from view) to the top, then fill the 250 * remaining with blanks at the bottom. 251 */ 252 253 /* Size decreasing. */ 254 if (sy < oldy) { 255 needed = oldy - sy; 256 257 /* Delete as many lines as possible from the bottom. */ 258 available = oldy - 1 - s->cy; 259 if (available > 0) { 260 if (available > needed) 261 available = needed; 262 grid_view_delete_lines(gd, oldy - available, available, 263 8); 264 } 265 needed -= available; 266 267 /* 268 * Now just increase the history size, if possible, to take 269 * over the lines which are left. If history is off, delete 270 * lines from the top. 271 */ 272 available = s->cy; 273 if (gd->flags & GRID_HISTORY) { 274 gd->hscrolled += needed; 275 gd->hsize += needed; 276 } else if (needed > 0 && available > 0) { 277 if (available > needed) 278 available = needed; 279 grid_view_delete_lines(gd, 0, available, 8); 280 } 281 s->cy -= needed; 282 } 283 284 /* Resize line array. */ 285 grid_adjust_lines(gd, gd->hsize + sy); 286 287 /* Size increasing. */ 288 if (sy > oldy) { 289 needed = sy - oldy; 290 291 /* 292 * Try to pull as much as possible out of scrolled history, if 293 * is is enabled. 294 */ 295 available = gd->hscrolled; 296 if (gd->flags & GRID_HISTORY && available > 0) { 297 if (available > needed) 298 available = needed; 299 gd->hscrolled -= available; 300 gd->hsize -= available; 301 s->cy += available; 302 } else 303 available = 0; 304 needed -= available; 305 306 /* Then fill the rest in with blanks. */ 307 for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) 308 memset(grid_get_line(gd, i), 0, sizeof(struct grid_line)); 309 } 310 311 /* Set the new size, and reset the scroll region. */ 312 gd->sy = sy; 313 s->rupper = 0; 314 s->rlower = screen_size_y(s) - 1; 315 } 316 317 /* Set selection. */ 318 void 319 screen_set_selection(struct screen *s, u_int sx, u_int sy, 320 u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc) 321 { 322 if (s->sel == NULL) 323 s->sel = xcalloc(1, sizeof *s->sel); 324 325 memcpy(&s->sel->cell, gc, sizeof s->sel->cell); 326 s->sel->hidden = 0; 327 s->sel->rectangle = rectangle; 328 s->sel->modekeys = modekeys; 329 330 s->sel->sx = sx; 331 s->sel->sy = sy; 332 s->sel->ex = ex; 333 s->sel->ey = ey; 334 } 335 336 /* Clear selection. */ 337 void 338 screen_clear_selection(struct screen *s) 339 { 340 free(s->sel); 341 s->sel = NULL; 342 } 343 344 /* Hide selection. */ 345 void 346 screen_hide_selection(struct screen *s) 347 { 348 if (s->sel != NULL) 349 s->sel->hidden = 1; 350 } 351 352 /* Check if cell in selection. */ 353 int 354 screen_check_selection(struct screen *s, u_int px, u_int py) 355 { 356 struct screen_sel *sel = s->sel; 357 u_int xx; 358 359 if (sel == NULL || sel->hidden) 360 return (0); 361 362 if (sel->rectangle) { 363 if (sel->sy < sel->ey) { 364 /* start line < end line -- downward selection. */ 365 if (py < sel->sy || py > sel->ey) 366 return (0); 367 } else if (sel->sy > sel->ey) { 368 /* start line > end line -- upward selection. */ 369 if (py > sel->sy || py < sel->ey) 370 return (0); 371 } else { 372 /* starting line == ending line. */ 373 if (py != sel->sy) 374 return (0); 375 } 376 377 /* 378 * Need to include the selection start row, but not the cursor 379 * row, which means the selection changes depending on which 380 * one is on the left. 381 */ 382 if (sel->ex < sel->sx) { 383 /* Cursor (ex) is on the left. */ 384 if (px < sel->ex) 385 return (0); 386 387 if (px > sel->sx) 388 return (0); 389 } else { 390 /* Selection start (sx) is on the left. */ 391 if (px < sel->sx) 392 return (0); 393 394 if (px > sel->ex) 395 return (0); 396 } 397 } else { 398 /* 399 * Like emacs, keep the top-left-most character, and drop the 400 * bottom-right-most, regardless of copy direction. 401 */ 402 if (sel->sy < sel->ey) { 403 /* starting line < ending line -- downward selection. */ 404 if (py < sel->sy || py > sel->ey) 405 return (0); 406 407 if (py == sel->sy && px < sel->sx) 408 return (0); 409 410 if (sel->modekeys == MODEKEY_EMACS) 411 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 412 else 413 xx = sel->ex; 414 if (py == sel->ey && px > xx) 415 return (0); 416 } else if (sel->sy > sel->ey) { 417 /* starting line > ending line -- upward selection. */ 418 if (py > sel->sy || py < sel->ey) 419 return (0); 420 421 if (py == sel->ey && px < sel->ex) 422 return (0); 423 424 if (sel->modekeys == MODEKEY_EMACS) 425 xx = sel->sx - 1; 426 else 427 xx = sel->sx; 428 if (py == sel->sy && (sel->sx == 0 || px > xx)) 429 return (0); 430 } else { 431 /* starting line == ending line. */ 432 if (py != sel->sy) 433 return (0); 434 435 if (sel->ex < sel->sx) { 436 /* cursor (ex) is on the left */ 437 if (sel->modekeys == MODEKEY_EMACS) 438 xx = sel->sx - 1; 439 else 440 xx = sel->sx; 441 if (px > xx || px < sel->ex) 442 return (0); 443 } else { 444 /* selection start (sx) is on the left */ 445 if (sel->modekeys == MODEKEY_EMACS) 446 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 447 else 448 xx = sel->ex; 449 if (px < sel->sx || px > xx) 450 return (0); 451 } 452 } 453 } 454 455 return (1); 456 } 457 458 /* Get selected grid cell. */ 459 void 460 screen_select_cell(struct screen *s, struct grid_cell *dst, 461 const struct grid_cell *src) 462 { 463 if (s->sel == NULL || s->sel->hidden) 464 return; 465 466 memcpy(dst, &s->sel->cell, sizeof *dst); 467 468 utf8_copy(&dst->data, &src->data); 469 dst->attr = dst->attr & ~GRID_ATTR_CHARSET; 470 dst->attr |= src->attr & GRID_ATTR_CHARSET; 471 dst->flags = src->flags; 472 } 473 474 /* Reflow wrapped lines. */ 475 static void 476 screen_reflow(struct screen *s, u_int new_x) 477 { 478 u_int cx = s->cx, cy = s->grid->hsize + s->cy, wx, wy; 479 struct timeval start, tv; 480 481 gettimeofday(&start, NULL); 482 483 grid_wrap_position(s->grid, cx, cy, &wx, &wy); 484 log_debug("%s: cursor %u,%u is %u,%u", __func__, cx, cy, wx, wy); 485 486 grid_reflow(s->grid, new_x); 487 488 grid_unwrap_position(s->grid, &cx, &cy, wx, wy); 489 log_debug("%s: new cursor is %u,%u", __func__, cx, cy); 490 491 if (cy >= s->grid->hsize) { 492 s->cx = cx; 493 s->cy = cy - s->grid->hsize; 494 } else { 495 s->cx = 0; 496 s->cy = 0; 497 } 498 499 gettimeofday(&tv, NULL); 500 timersub(&tv, &start, &tv); 501 502 log_debug("%s: reflow took %llu.%06u seconds", __func__, 503 (unsigned long long)tv.tv_sec, (u_int)tv.tv_usec); 504 } 505