screen.c revision 1.8.4.2 1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott (at) 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