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