1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2007 Nicholas Marriott <[email protected]> |
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 | int |
155 | screen_set_title(struct screen *s, const char *title) |
156 | { |
157 | if (!utf8_isvalid(title)) |
158 | return (0); |
159 | free(s->title); |
160 | s->title = xstrdup(title); |
161 | return (1); |
162 | } |
163 | |
164 | /* Set screen path. */ |
165 | void |
166 | screen_set_path(struct screen *s, const char *path) |
167 | { |
168 | free(s->path); |
169 | utf8_stravis(&s->path, path, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); |
170 | } |
171 | |
172 | /* Push the current title onto the stack. */ |
173 | void |
174 | screen_push_title(struct screen *s) |
175 | { |
176 | struct screen_title_entry *title_entry; |
177 | |
178 | if (s->titles == NULL) { |
179 | s->titles = xmalloc(sizeof *s->titles); |
180 | TAILQ_INIT(s->titles); |
181 | } |
182 | title_entry = xmalloc(sizeof *title_entry); |
183 | title_entry->text = xstrdup(s->title); |
184 | TAILQ_INSERT_HEAD(s->titles, title_entry, entry); |
185 | } |
186 | |
187 | /* |
188 | * Pop a title from the stack and set it as the screen title. If the stack is |
189 | * empty, do nothing. |
190 | */ |
191 | void |
192 | screen_pop_title(struct screen *s) |
193 | { |
194 | struct screen_title_entry *title_entry; |
195 | |
196 | if (s->titles == NULL) |
197 | return; |
198 | |
199 | title_entry = TAILQ_FIRST(s->titles); |
200 | if (title_entry != NULL) { |
201 | screen_set_title(s, title_entry->text); |
202 | |
203 | TAILQ_REMOVE(s->titles, title_entry, entry); |
204 | free(title_entry->text); |
205 | free(title_entry); |
206 | } |
207 | } |
208 | |
209 | /* Resize screen. */ |
210 | void |
211 | screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) |
212 | { |
213 | if (sx < 1) |
214 | sx = 1; |
215 | if (sy < 1) |
216 | sy = 1; |
217 | |
218 | if (sx != screen_size_x(s)) { |
219 | s->grid->sx = sx; |
220 | screen_reset_tabs(s); |
221 | } else |
222 | reflow = 0; |
223 | |
224 | if (sy != screen_size_y(s)) |
225 | screen_resize_y(s, sy); |
226 | |
227 | if (reflow) |
228 | screen_reflow(s, sx); |
229 | } |
230 | |
231 | static void |
232 | screen_resize_y(struct screen *s, u_int sy) |
233 | { |
234 | struct grid *gd = s->grid; |
235 | u_int needed, available, oldy, i; |
236 | |
237 | if (sy == 0) |
238 | fatalx("zero size" ); |
239 | oldy = screen_size_y(s); |
240 | |
241 | /* |
242 | * When resizing: |
243 | * |
244 | * If the height is decreasing, delete lines from the bottom until |
245 | * hitting the cursor, then push lines from the top into the history. |
246 | * |
247 | * When increasing, pull as many lines as possible from scrolled |
248 | * history (not explicitly cleared from view) to the top, then fill the |
249 | * remaining with blanks at the bottom. |
250 | */ |
251 | |
252 | /* Size decreasing. */ |
253 | if (sy < oldy) { |
254 | needed = oldy - sy; |
255 | |
256 | /* Delete as many lines as possible from the bottom. */ |
257 | available = oldy - 1 - s->cy; |
258 | if (available > 0) { |
259 | if (available > needed) |
260 | available = needed; |
261 | grid_view_delete_lines(gd, oldy - available, available, |
262 | 8); |
263 | } |
264 | needed -= available; |
265 | |
266 | /* |
267 | * Now just increase the history size, if possible, to take |
268 | * over the lines which are left. If history is off, delete |
269 | * lines from the top. |
270 | */ |
271 | available = s->cy; |
272 | if (gd->flags & GRID_HISTORY) { |
273 | gd->hscrolled += needed; |
274 | gd->hsize += needed; |
275 | } else if (needed > 0 && available > 0) { |
276 | if (available > needed) |
277 | available = needed; |
278 | grid_view_delete_lines(gd, 0, available, 8); |
279 | } |
280 | s->cy -= needed; |
281 | } |
282 | |
283 | /* Resize line array. */ |
284 | grid_adjust_lines(gd, gd->hsize + sy); |
285 | |
286 | /* Size increasing. */ |
287 | if (sy > oldy) { |
288 | needed = sy - oldy; |
289 | |
290 | /* |
291 | * Try to pull as much as possible out of scrolled history, if |
292 | * is is enabled. |
293 | */ |
294 | available = gd->hscrolled; |
295 | if (gd->flags & GRID_HISTORY && available > 0) { |
296 | if (available > needed) |
297 | available = needed; |
298 | gd->hscrolled -= available; |
299 | gd->hsize -= available; |
300 | s->cy += available; |
301 | } else |
302 | available = 0; |
303 | needed -= available; |
304 | |
305 | /* Then fill the rest in with blanks. */ |
306 | for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) |
307 | memset(grid_get_line(gd, i), 0, sizeof(struct grid_line)); |
308 | } |
309 | |
310 | /* Set the new size, and reset the scroll region. */ |
311 | gd->sy = sy; |
312 | s->rupper = 0; |
313 | s->rlower = screen_size_y(s) - 1; |
314 | } |
315 | |
316 | /* Set selection. */ |
317 | void |
318 | screen_set_selection(struct screen *s, u_int sx, u_int sy, |
319 | u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc) |
320 | { |
321 | if (s->sel == NULL) |
322 | s->sel = xcalloc(1, sizeof *s->sel); |
323 | |
324 | memcpy(&s->sel->cell, gc, sizeof s->sel->cell); |
325 | s->sel->hidden = 0; |
326 | s->sel->rectangle = rectangle; |
327 | s->sel->modekeys = modekeys; |
328 | |
329 | s->sel->sx = sx; |
330 | s->sel->sy = sy; |
331 | s->sel->ex = ex; |
332 | s->sel->ey = ey; |
333 | } |
334 | |
335 | /* Clear selection. */ |
336 | void |
337 | screen_clear_selection(struct screen *s) |
338 | { |
339 | free(s->sel); |
340 | s->sel = NULL; |
341 | } |
342 | |
343 | /* Hide selection. */ |
344 | void |
345 | screen_hide_selection(struct screen *s) |
346 | { |
347 | if (s->sel != NULL) |
348 | s->sel->hidden = 1; |
349 | } |
350 | |
351 | /* Check if cell in selection. */ |
352 | int |
353 | screen_check_selection(struct screen *s, u_int px, u_int py) |
354 | { |
355 | struct screen_sel *sel = s->sel; |
356 | u_int xx; |
357 | |
358 | if (sel == NULL || sel->hidden) |
359 | return (0); |
360 | |
361 | if (sel->rectangle) { |
362 | if (sel->sy < sel->ey) { |
363 | /* start line < end line -- downward selection. */ |
364 | if (py < sel->sy || py > sel->ey) |
365 | return (0); |
366 | } else if (sel->sy > sel->ey) { |
367 | /* start line > end line -- upward selection. */ |
368 | if (py > sel->sy || py < sel->ey) |
369 | return (0); |
370 | } else { |
371 | /* starting line == ending line. */ |
372 | if (py != sel->sy) |
373 | return (0); |
374 | } |
375 | |
376 | /* |
377 | * Need to include the selection start row, but not the cursor |
378 | * row, which means the selection changes depending on which |
379 | * one is on the left. |
380 | */ |
381 | if (sel->ex < sel->sx) { |
382 | /* Cursor (ex) is on the left. */ |
383 | if (px < sel->ex) |
384 | return (0); |
385 | |
386 | if (px > sel->sx) |
387 | return (0); |
388 | } else { |
389 | /* Selection start (sx) is on the left. */ |
390 | if (px < sel->sx) |
391 | return (0); |
392 | |
393 | if (px > sel->ex) |
394 | return (0); |
395 | } |
396 | } else { |
397 | /* |
398 | * Like emacs, keep the top-left-most character, and drop the |
399 | * bottom-right-most, regardless of copy direction. |
400 | */ |
401 | if (sel->sy < sel->ey) { |
402 | /* starting line < ending line -- downward selection. */ |
403 | if (py < sel->sy || py > sel->ey) |
404 | return (0); |
405 | |
406 | if (py == sel->sy && px < sel->sx) |
407 | return (0); |
408 | |
409 | if (sel->modekeys == MODEKEY_EMACS) |
410 | xx = (sel->ex == 0 ? 0 : sel->ex - 1); |
411 | else |
412 | xx = sel->ex; |
413 | if (py == sel->ey && px > xx) |
414 | return (0); |
415 | } else if (sel->sy > sel->ey) { |
416 | /* starting line > ending line -- upward selection. */ |
417 | if (py > sel->sy || py < sel->ey) |
418 | return (0); |
419 | |
420 | if (py == sel->ey && px < sel->ex) |
421 | return (0); |
422 | |
423 | if (sel->modekeys == MODEKEY_EMACS) |
424 | xx = sel->sx - 1; |
425 | else |
426 | xx = sel->sx; |
427 | if (py == sel->sy && (sel->sx == 0 || px > xx)) |
428 | return (0); |
429 | } else { |
430 | /* starting line == ending line. */ |
431 | if (py != sel->sy) |
432 | return (0); |
433 | |
434 | if (sel->ex < sel->sx) { |
435 | /* cursor (ex) is on the left */ |
436 | if (sel->modekeys == MODEKEY_EMACS) |
437 | xx = sel->sx - 1; |
438 | else |
439 | xx = sel->sx; |
440 | if (px > xx || px < sel->ex) |
441 | return (0); |
442 | } else { |
443 | /* selection start (sx) is on the left */ |
444 | if (sel->modekeys == MODEKEY_EMACS) |
445 | xx = (sel->ex == 0 ? 0 : sel->ex - 1); |
446 | else |
447 | xx = sel->ex; |
448 | if (px < sel->sx || px > xx) |
449 | return (0); |
450 | } |
451 | } |
452 | } |
453 | |
454 | return (1); |
455 | } |
456 | |
457 | /* Get selected grid cell. */ |
458 | void |
459 | screen_select_cell(struct screen *s, struct grid_cell *dst, |
460 | const struct grid_cell *src) |
461 | { |
462 | if (s->sel == NULL || s->sel->hidden) |
463 | return; |
464 | |
465 | memcpy(dst, &s->sel->cell, sizeof *dst); |
466 | |
467 | utf8_copy(&dst->data, &src->data); |
468 | dst->attr = dst->attr & ~GRID_ATTR_CHARSET; |
469 | dst->attr |= src->attr & GRID_ATTR_CHARSET; |
470 | dst->flags = src->flags; |
471 | } |
472 | |
473 | /* Reflow wrapped lines. */ |
474 | static void |
475 | screen_reflow(struct screen *s, u_int new_x) |
476 | { |
477 | u_int cx = s->cx, cy = s->grid->hsize + s->cy, wx, wy; |
478 | struct timeval start, tv; |
479 | |
480 | gettimeofday(&start, NULL); |
481 | |
482 | grid_wrap_position(s->grid, cx, cy, &wx, &wy); |
483 | log_debug("%s: cursor %u,%u is %u,%u" , __func__, cx, cy, wx, wy); |
484 | |
485 | grid_reflow(s->grid, new_x); |
486 | |
487 | grid_unwrap_position(s->grid, &cx, &cy, wx, wy); |
488 | log_debug("%s: new cursor is %u,%u" , __func__, cx, cy); |
489 | |
490 | if (cy >= s->grid->hsize) { |
491 | s->cx = cx; |
492 | s->cy = cy - s->grid->hsize; |
493 | } else { |
494 | s->cx = 0; |
495 | s->cy = 0; |
496 | } |
497 | |
498 | gettimeofday(&tv, NULL); |
499 | timersub(&tv, &start, &tv); |
500 | |
501 | log_debug("%s: reflow took %llu.%06u seconds" , __func__, |
502 | (unsigned long long)tv.tv_sec, (u_int)tv.tv_usec); |
503 | } |
504 | |