xref: /netbsd-src/external/bsd/tmux/dist/screen.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
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, int, u_int *);
51 static void	screen_reflow(struct screen *, u_int, u_int *, u_int *, int);
52 
53 /* Free titles stack. */
54 static void
55 screen_free_titles(struct screen *s)
56 {
57 	struct screen_title_entry	*title_entry;
58 
59 	if (s->titles == NULL)
60 		return;
61 
62 	while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) {
63 		TAILQ_REMOVE(s->titles, title_entry, entry);
64 		free(title_entry->text);
65 		free(title_entry);
66 	}
67 
68 	free(s->titles);
69 	s->titles = NULL;
70 }
71 
72 /* Create a new screen. */
73 void
74 screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
75 {
76 	s->grid = grid_create(sx, sy, hlimit);
77 	s->saved_grid = NULL;
78 
79 	s->title = xstrdup("");
80 	s->titles = NULL;
81 	s->path = NULL;
82 
83 	s->cstyle = 0;
84 	s->ccolour = xstrdup("");
85 	s->tabs = NULL;
86 	s->sel = NULL;
87 
88 	s->write_list = NULL;
89 
90 	screen_reinit(s);
91 }
92 
93 /* Reinitialise screen. */
94 void
95 screen_reinit(struct screen *s)
96 {
97 	s->cx = 0;
98 	s->cy = 0;
99 
100 	s->rupper = 0;
101 	s->rlower = screen_size_y(s) - 1;
102 
103 	s->mode = MODE_CURSOR | MODE_WRAP;
104 
105 	if (s->saved_grid != NULL)
106 		screen_alternate_off(s, NULL, 0);
107 	s->saved_cx = UINT_MAX;
108 	s->saved_cy = UINT_MAX;
109 
110 	screen_reset_tabs(s);
111 
112 	grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8);
113 
114 	screen_clear_selection(s);
115 	screen_free_titles(s);
116 }
117 
118 /* Destroy a screen. */
119 void
120 screen_free(struct screen *s)
121 {
122 	free(s->sel);
123 	free(s->tabs);
124 	free(s->path);
125 	free(s->title);
126 	free(s->ccolour);
127 
128 	if (s->write_list != NULL)
129 		screen_write_free_list(s);
130 
131 	if (s->saved_grid != NULL)
132 		grid_destroy(s->saved_grid);
133 	grid_destroy(s->grid);
134 
135 	screen_free_titles(s);
136 }
137 
138 /* Reset tabs to default, eight spaces apart. */
139 void
140 screen_reset_tabs(struct screen *s)
141 {
142 	u_int	i;
143 
144 	free(s->tabs);
145 
146 	if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL)
147 		fatal("bit_alloc failed");
148 	for (i = 8; i < screen_size_x(s); i += 8)
149 		bit_set(s->tabs, i);
150 }
151 
152 /* Set screen cursor style. */
153 void
154 screen_set_cursor_style(struct screen *s, u_int style)
155 {
156 	if (style <= 6) {
157 		s->cstyle = style;
158 		s->mode &= ~MODE_BLINKING;
159 	}
160 }
161 
162 /* Set screen cursor colour. */
163 void
164 screen_set_cursor_colour(struct screen *s, const char *colour)
165 {
166 	free(s->ccolour);
167 	s->ccolour = xstrdup(colour);
168 }
169 
170 /* Set screen title. */
171 int
172 screen_set_title(struct screen *s, const char *title)
173 {
174 	if (!utf8_isvalid(title))
175 		return (0);
176 	free(s->title);
177 	s->title = xstrdup(title);
178 	return (1);
179 }
180 
181 /* Set screen path. */
182 void
183 screen_set_path(struct screen *s, const char *path)
184 {
185 	free(s->path);
186 	utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
187 }
188 
189 /* Push the current title onto the stack. */
190 void
191 screen_push_title(struct screen *s)
192 {
193 	struct screen_title_entry *title_entry;
194 
195 	if (s->titles == NULL) {
196 		s->titles = xmalloc(sizeof *s->titles);
197 		TAILQ_INIT(s->titles);
198 	}
199 	title_entry = xmalloc(sizeof *title_entry);
200 	title_entry->text = xstrdup(s->title);
201 	TAILQ_INSERT_HEAD(s->titles, title_entry, entry);
202 }
203 
204 /*
205  * Pop a title from the stack and set it as the screen title. If the stack is
206  * empty, do nothing.
207  */
208 void
209 screen_pop_title(struct screen *s)
210 {
211 	struct screen_title_entry *title_entry;
212 
213 	if (s->titles == NULL)
214 		return;
215 
216 	title_entry = TAILQ_FIRST(s->titles);
217 	if (title_entry != NULL) {
218 		screen_set_title(s, title_entry->text);
219 
220 		TAILQ_REMOVE(s->titles, title_entry, entry);
221 		free(title_entry->text);
222 		free(title_entry);
223 	}
224 }
225 
226 /* Resize screen with options. */
227 void
228 screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
229     int eat_empty, int cursor)
230 {
231 	u_int	cx = s->cx, cy = s->grid->hsize + s->cy;
232 
233 	if (s->write_list != NULL)
234 		screen_write_free_list(s);
235 
236 	log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)",
237 	    __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy,
238 	    cx, cy);
239 
240 	if (sx < 1)
241 		sx = 1;
242 	if (sy < 1)
243 		sy = 1;
244 
245 	if (sx != screen_size_x(s)) {
246 		s->grid->sx = sx;
247 		screen_reset_tabs(s);
248 	} else
249 		reflow = 0;
250 
251 	if (sy != screen_size_y(s))
252 		screen_resize_y(s, sy, eat_empty, &cy);
253 
254 	if (reflow)
255 		screen_reflow(s, sx, &cx, &cy, cursor);
256 
257 	if (cy >= s->grid->hsize) {
258 		s->cx = cx;
259 		s->cy = cy - s->grid->hsize;
260 	} else {
261 		s->cx = 0;
262 		s->cy = 0;
263 	}
264 
265 	log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx,
266 	    s->cy, cx, cy);
267 
268 	if (s->write_list != NULL)
269 		screen_write_make_list(s);
270 }
271 
272 /* Resize screen. */
273 void
274 screen_resize(struct screen *s, u_int sx, u_int sy, int reflow)
275 {
276 	screen_resize_cursor(s, sx, sy, reflow, 1, 1);
277 }
278 
279 static void
280 screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy)
281 {
282 	struct grid	*gd = s->grid;
283 	u_int		 needed, available, oldy, i;
284 
285 	if (sy == 0)
286 		fatalx("zero size");
287 	oldy = screen_size_y(s);
288 
289 	/*
290 	 * When resizing:
291 	 *
292 	 * If the height is decreasing, delete lines from the bottom until
293 	 * hitting the cursor, then push lines from the top into the history.
294 	 *
295 	 * When increasing, pull as many lines as possible from scrolled
296 	 * history (not explicitly cleared from view) to the top, then fill the
297 	 * remaining with blanks at the bottom.
298 	 */
299 
300 	/* Size decreasing. */
301 	if (sy < oldy) {
302 		needed = oldy - sy;
303 
304 		/* Delete as many lines as possible from the bottom. */
305 		if (eat_empty) {
306 			available = oldy - 1 - s->cy;
307 			if (available > 0) {
308 				if (available > needed)
309 					available = needed;
310 				grid_view_delete_lines(gd, oldy - available,
311 				    available, 8);
312 			}
313 			needed -= available;
314 		}
315 
316 		/*
317 		 * Now just increase the history size, if possible, to take
318 		 * over the lines which are left. If history is off, delete
319 		 * lines from the top.
320 		 */
321 		available = s->cy;
322 		if (gd->flags & GRID_HISTORY) {
323 			gd->hscrolled += needed;
324 			gd->hsize += needed;
325 		} else if (needed > 0 && available > 0) {
326 			if (available > needed)
327 				available = needed;
328 			grid_view_delete_lines(gd, 0, available, 8);
329 			(*cy) -= available;
330 		}
331 	}
332 
333 	/* Resize line array. */
334 	grid_adjust_lines(gd, gd->hsize + sy);
335 
336 	/* Size increasing. */
337 	if (sy > oldy) {
338 		needed = sy - oldy;
339 
340 		/*
341 		 * Try to pull as much as possible out of scrolled history, if
342 		 * is is enabled.
343 		 */
344 		available = gd->hscrolled;
345 		if (gd->flags & GRID_HISTORY && available > 0) {
346 			if (available > needed)
347 				available = needed;
348 			gd->hscrolled -= available;
349 			gd->hsize -= available;
350 		} else
351 			available = 0;
352 		needed -= available;
353 
354 		/* Then fill the rest in with blanks. */
355 		for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++)
356 			grid_empty_line(gd, i, 8);
357 	}
358 
359 	/* Set the new size, and reset the scroll region. */
360 	gd->sy = sy;
361 	s->rupper = 0;
362 	s->rlower = screen_size_y(s) - 1;
363 }
364 
365 /* Set selection. */
366 void
367 screen_set_selection(struct screen *s, u_int sx, u_int sy,
368     u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc)
369 {
370 	if (s->sel == NULL)
371 		s->sel = xcalloc(1, sizeof *s->sel);
372 
373 	memcpy(&s->sel->cell, gc, sizeof s->sel->cell);
374 	s->sel->hidden = 0;
375 	s->sel->rectangle = rectangle;
376 	s->sel->modekeys = modekeys;
377 
378 	s->sel->sx = sx;
379 	s->sel->sy = sy;
380 	s->sel->ex = ex;
381 	s->sel->ey = ey;
382 }
383 
384 /* Clear selection. */
385 void
386 screen_clear_selection(struct screen *s)
387 {
388 	free(s->sel);
389 	s->sel = NULL;
390 }
391 
392 /* Hide selection. */
393 void
394 screen_hide_selection(struct screen *s)
395 {
396 	if (s->sel != NULL)
397 		s->sel->hidden = 1;
398 }
399 
400 /* Check if cell in selection. */
401 int
402 screen_check_selection(struct screen *s, u_int px, u_int py)
403 {
404 	struct screen_sel	*sel = s->sel;
405 	u_int			 xx;
406 
407 	if (sel == NULL || sel->hidden)
408 		return (0);
409 
410 	if (sel->rectangle) {
411 		if (sel->sy < sel->ey) {
412 			/* start line < end line -- downward selection. */
413 			if (py < sel->sy || py > sel->ey)
414 				return (0);
415 		} else if (sel->sy > sel->ey) {
416 			/* start line > end line -- upward selection. */
417 			if (py > sel->sy || py < sel->ey)
418 				return (0);
419 		} else {
420 			/* starting line == ending line. */
421 			if (py != sel->sy)
422 				return (0);
423 		}
424 
425 		/*
426 		 * Need to include the selection start row, but not the cursor
427 		 * row, which means the selection changes depending on which
428 		 * one is on the left.
429 		 */
430 		if (sel->ex < sel->sx) {
431 			/* Cursor (ex) is on the left. */
432 			if (px < sel->ex)
433 				return (0);
434 
435 			if (px > sel->sx)
436 				return (0);
437 		} else {
438 			/* Selection start (sx) is on the left. */
439 			if (px < sel->sx)
440 				return (0);
441 
442 			if (px > sel->ex)
443 				return (0);
444 		}
445 	} else {
446 		/*
447 		 * Like emacs, keep the top-left-most character, and drop the
448 		 * bottom-right-most, regardless of copy direction.
449 		 */
450 		if (sel->sy < sel->ey) {
451 			/* starting line < ending line -- downward selection. */
452 			if (py < sel->sy || py > sel->ey)
453 				return (0);
454 
455 			if (py == sel->sy && px < sel->sx)
456 				return (0);
457 
458 			if (sel->modekeys == MODEKEY_EMACS)
459 				xx = (sel->ex == 0 ? 0 : sel->ex - 1);
460 			else
461 				xx = sel->ex;
462 			if (py == sel->ey && px > xx)
463 				return (0);
464 		} else if (sel->sy > sel->ey) {
465 			/* starting line > ending line -- upward selection. */
466 			if (py > sel->sy || py < sel->ey)
467 				return (0);
468 
469 			if (py == sel->ey && px < sel->ex)
470 				return (0);
471 
472 			if (sel->modekeys == MODEKEY_EMACS)
473 				xx = sel->sx - 1;
474 			else
475 				xx = sel->sx;
476 			if (py == sel->sy && (sel->sx == 0 || px > xx))
477 				return (0);
478 		} else {
479 			/* starting line == ending line. */
480 			if (py != sel->sy)
481 				return (0);
482 
483 			if (sel->ex < sel->sx) {
484 				/* cursor (ex) is on the left */
485 				if (sel->modekeys == MODEKEY_EMACS)
486 					xx = sel->sx - 1;
487 				else
488 					xx = sel->sx;
489 				if (px > xx || px < sel->ex)
490 					return (0);
491 			} else {
492 				/* selection start (sx) is on the left */
493 				if (sel->modekeys == MODEKEY_EMACS)
494 					xx = (sel->ex == 0 ? 0 : sel->ex - 1);
495 				else
496 					xx = sel->ex;
497 				if (px < sel->sx || px > xx)
498 					return (0);
499 			}
500 		}
501 	}
502 
503 	return (1);
504 }
505 
506 /* Get selected grid cell. */
507 void
508 screen_select_cell(struct screen *s, struct grid_cell *dst,
509     const struct grid_cell *src)
510 {
511 	if (s->sel == NULL || s->sel->hidden)
512 		return;
513 
514 	memcpy(dst, &s->sel->cell, sizeof *dst);
515 
516 	utf8_copy(&dst->data, &src->data);
517 	dst->attr = dst->attr & ~GRID_ATTR_CHARSET;
518 	dst->attr |= src->attr & GRID_ATTR_CHARSET;
519 	dst->flags = src->flags;
520 }
521 
522 /* Reflow wrapped lines. */
523 static void
524 screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor)
525 {
526 	u_int	wx, wy;
527 
528 	if (cursor) {
529 		grid_wrap_position(s->grid, *cx, *cy, &wx, &wy);
530 		log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx,
531 		    wy);
532 	}
533 
534 	grid_reflow(s->grid, new_x);
535 
536 	if (cursor) {
537 		grid_unwrap_position(s->grid, cx, cy, wx, wy);
538 		log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy);
539 	}
540 	else {
541 		*cx = 0;
542 		*cy = s->grid->hsize;
543 	}
544 }
545 
546 /*
547  * Enter alternative screen mode. A copy of the visible screen is saved and the
548  * history is not updated.
549  */
550 void
551 screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
552 {
553 	u_int	sx, sy;
554 
555 	if (s->saved_grid != NULL)
556 		return;
557 	sx = screen_size_x(s);
558 	sy = screen_size_y(s);
559 
560 	s->saved_grid = grid_create(sx, sy, 0);
561 	grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy);
562 	if (cursor) {
563 		s->saved_cx = s->cx;
564 		s->saved_cy = s->cy;
565 	}
566 	memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
567 
568 	grid_view_clear(s->grid, 0, 0, sx, sy, 8);
569 
570 	s->saved_flags = s->grid->flags;
571 	s->grid->flags &= ~GRID_HISTORY;
572 }
573 
574 /* Exit alternate screen mode and restore the copied grid. */
575 void
576 screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
577 {
578 	u_int	sx = screen_size_x(s), sy = screen_size_y(s);
579 
580 	/*
581 	 * If the current size is different, temporarily resize to the old size
582 	 * before copying back.
583 	 */
584 	if (s->saved_grid != NULL)
585 		screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 1);
586 
587 	/*
588 	 * Restore the cursor position and cell. This happens even if not
589 	 * currently in the alternate screen.
590 	 */
591 	if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) {
592 		s->cx = s->saved_cx;
593 		s->cy = s->saved_cy;
594 		if (gc != NULL)
595 			memcpy(gc, &s->saved_cell, sizeof *gc);
596 	}
597 
598 	/* If not in the alternate screen, do nothing more. */
599 	if (s->saved_grid == NULL) {
600 		if (s->cx > screen_size_x(s) - 1)
601 			s->cx = screen_size_x(s) - 1;
602 		if (s->cy > screen_size_y(s) - 1)
603 			s->cy = screen_size_y(s) - 1;
604 		return;
605 	}
606 
607 	/* Restore the saved grid. */
608 	grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0,
609 	    s->saved_grid->sy);
610 
611 	/*
612 	 * Turn history back on (so resize can use it) and then resize back to
613 	 * the current size.
614 	 */
615 	if (s->saved_flags & GRID_HISTORY)
616 		s->grid->flags |= GRID_HISTORY;
617 	screen_resize(s, sx, sy, 1);
618 
619 	grid_destroy(s->saved_grid);
620 	s->saved_grid = NULL;
621 
622 	if (s->cx > screen_size_x(s) - 1)
623 		s->cx = screen_size_x(s) - 1;
624 	if (s->cy > screen_size_y(s) - 1)
625 		s->cy = screen_size_y(s) - 1;
626 }
627