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