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. */
28struct 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. */
43struct screen_title_entry {
44 char *text;
45
46 TAILQ_ENTRY(screen_title_entry) entry;
47};
48TAILQ_HEAD(screen_titles, screen_title_entry);
49
50static void screen_resize_y(struct screen *, u_int);
51
52static void screen_reflow(struct screen *, u_int);
53
54/* Free titles stack. */
55static void
56screen_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. */
74void
75screen_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. */
90void
91screen_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. */
110void
111screen_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. */
124void
125screen_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. */
138void
139screen_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. */
146void
147screen_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. */
154int
155screen_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. */
165void
166screen_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. */
173void
174screen_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 */
191void
192screen_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. */
210void
211screen_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
231static void
232screen_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. */
317void
318screen_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. */
336void
337screen_clear_selection(struct screen *s)
338{
339 free(s->sel);
340 s->sel = NULL;
341}
342
343/* Hide selection. */
344void
345screen_hide_selection(struct screen *s)
346{
347 if (s->sel != NULL)
348 s->sel->hidden = 1;
349}
350
351/* Check if cell in selection. */
352int
353screen_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. */
458void
459screen_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. */
474static void
475screen_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