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