screen.c revision 1.5.2.1 1 1.5 christos /* $OpenBSD$ */
2 1.1 jmmv
3 1.1 jmmv /*
4 1.1 jmmv * Copyright (c) 2007 Nicholas Marriott <nicm (at) users.sourceforge.net>
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.5.2.1 pgoyette static void screen_resize_x(struct screen *, u_int);
28 1.5.2.1 pgoyette static void screen_resize_y(struct screen *, u_int);
29 1.5.2.1 pgoyette
30 1.5.2.1 pgoyette static void screen_reflow(struct screen *, u_int);
31 1.1 jmmv
32 1.1 jmmv /* Create a new screen. */
33 1.1 jmmv void
34 1.1 jmmv screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
35 1.1 jmmv {
36 1.1 jmmv s->grid = grid_create(sx, sy, hlimit);
37 1.5 christos s->title = xstrdup("");
38 1.1 jmmv
39 1.3 jmmv s->cstyle = 0;
40 1.3 jmmv s->ccolour = xstrdup("");
41 1.1 jmmv s->tabs = NULL;
42 1.1 jmmv
43 1.1 jmmv screen_reinit(s);
44 1.1 jmmv }
45 1.1 jmmv
46 1.1 jmmv /* Reinitialise screen. */
47 1.1 jmmv void
48 1.1 jmmv screen_reinit(struct screen *s)
49 1.1 jmmv {
50 1.1 jmmv s->cx = 0;
51 1.1 jmmv s->cy = 0;
52 1.1 jmmv
53 1.1 jmmv s->rupper = 0;
54 1.1 jmmv s->rlower = screen_size_y(s) - 1;
55 1.1 jmmv
56 1.1 jmmv s->mode = MODE_CURSOR | MODE_WRAP;
57 1.1 jmmv
58 1.1 jmmv screen_reset_tabs(s);
59 1.1 jmmv
60 1.5.2.1 pgoyette grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8);
61 1.1 jmmv
62 1.1 jmmv screen_clear_selection(s);
63 1.1 jmmv }
64 1.1 jmmv
65 1.1 jmmv /* Destroy a screen. */
66 1.1 jmmv void
67 1.1 jmmv screen_free(struct screen *s)
68 1.1 jmmv {
69 1.4 christos free(s->tabs);
70 1.4 christos free(s->title);
71 1.4 christos free(s->ccolour);
72 1.1 jmmv grid_destroy(s->grid);
73 1.1 jmmv }
74 1.1 jmmv
75 1.1 jmmv /* Reset tabs to default, eight spaces apart. */
76 1.1 jmmv void
77 1.1 jmmv screen_reset_tabs(struct screen *s)
78 1.1 jmmv {
79 1.1 jmmv u_int i;
80 1.1 jmmv
81 1.4 christos free(s->tabs);
82 1.1 jmmv
83 1.1 jmmv if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL)
84 1.1 jmmv fatal("bit_alloc failed");
85 1.1 jmmv for (i = 8; i < screen_size_x(s); i += 8)
86 1.1 jmmv bit_set(s->tabs, i);
87 1.1 jmmv }
88 1.1 jmmv
89 1.3 jmmv /* Set screen cursor style. */
90 1.3 jmmv void
91 1.3 jmmv screen_set_cursor_style(struct screen *s, u_int style)
92 1.3 jmmv {
93 1.4 christos if (style <= 6)
94 1.3 jmmv s->cstyle = style;
95 1.3 jmmv }
96 1.3 jmmv
97 1.3 jmmv /* Set screen cursor colour. */
98 1.3 jmmv void
99 1.3 jmmv screen_set_cursor_colour(struct screen *s, const char *colour_string)
100 1.3 jmmv {
101 1.4 christos free(s->ccolour);
102 1.3 jmmv s->ccolour = xstrdup(colour_string);
103 1.3 jmmv }
104 1.3 jmmv
105 1.1 jmmv /* Set screen title. */
106 1.1 jmmv void
107 1.1 jmmv screen_set_title(struct screen *s, const char *title)
108 1.1 jmmv {
109 1.4 christos free(s->title);
110 1.4 christos s->title = xstrdup(title);
111 1.1 jmmv }
112 1.1 jmmv
113 1.1 jmmv /* Resize screen. */
114 1.1 jmmv void
115 1.4 christos screen_resize(struct screen *s, u_int sx, u_int sy, int reflow)
116 1.1 jmmv {
117 1.1 jmmv if (sx < 1)
118 1.1 jmmv sx = 1;
119 1.1 jmmv if (sy < 1)
120 1.1 jmmv sy = 1;
121 1.1 jmmv
122 1.1 jmmv if (sx != screen_size_x(s)) {
123 1.1 jmmv screen_resize_x(s, sx);
124 1.1 jmmv
125 1.1 jmmv /*
126 1.1 jmmv * It is unclear what should happen to tabs on resize. xterm
127 1.1 jmmv * seems to try and maintain them, rxvt resets them. Resetting
128 1.1 jmmv * is simpler and more reliable so let's do that.
129 1.1 jmmv */
130 1.1 jmmv screen_reset_tabs(s);
131 1.1 jmmv }
132 1.1 jmmv
133 1.1 jmmv if (sy != screen_size_y(s))
134 1.1 jmmv screen_resize_y(s, sy);
135 1.4 christos
136 1.4 christos if (reflow)
137 1.4 christos screen_reflow(s, sx);
138 1.1 jmmv }
139 1.1 jmmv
140 1.5.2.1 pgoyette static void
141 1.1 jmmv screen_resize_x(struct screen *s, u_int sx)
142 1.1 jmmv {
143 1.1 jmmv struct grid *gd = s->grid;
144 1.1 jmmv
145 1.1 jmmv if (sx == 0)
146 1.1 jmmv fatalx("zero size");
147 1.1 jmmv
148 1.1 jmmv /*
149 1.1 jmmv * Treat resizing horizontally simply: just ensure the cursor is
150 1.1 jmmv * on-screen and change the size. Don't bother to truncate any lines -
151 1.5.2.1 pgoyette * then the data should be accessible if the size is then increased.
152 1.1 jmmv *
153 1.1 jmmv * The only potential wrinkle is if UTF-8 double-width characters are
154 1.1 jmmv * left in the last column, but UTF-8 terminals should deal with this
155 1.1 jmmv * sanely.
156 1.1 jmmv */
157 1.1 jmmv if (s->cx >= sx)
158 1.1 jmmv s->cx = sx - 1;
159 1.1 jmmv gd->sx = sx;
160 1.1 jmmv }
161 1.1 jmmv
162 1.5.2.1 pgoyette static void
163 1.1 jmmv screen_resize_y(struct screen *s, u_int sy)
164 1.1 jmmv {
165 1.1 jmmv struct grid *gd = s->grid;
166 1.1 jmmv u_int needed, available, oldy, i;
167 1.1 jmmv
168 1.1 jmmv if (sy == 0)
169 1.1 jmmv fatalx("zero size");
170 1.1 jmmv oldy = screen_size_y(s);
171 1.1 jmmv
172 1.1 jmmv /*
173 1.1 jmmv * When resizing:
174 1.1 jmmv *
175 1.1 jmmv * If the height is decreasing, delete lines from the bottom until
176 1.1 jmmv * hitting the cursor, then push lines from the top into the history.
177 1.1 jmmv *
178 1.5.2.1 pgoyette * When increasing, pull as many lines as possible from scrolled
179 1.5.2.1 pgoyette * history (not explicitly cleared from view) to the top, then fill the
180 1.5.2.1 pgoyette * remaining with blanks at the bottom.
181 1.1 jmmv */
182 1.1 jmmv
183 1.1 jmmv /* Size decreasing. */
184 1.1 jmmv if (sy < oldy) {
185 1.1 jmmv needed = oldy - sy;
186 1.1 jmmv
187 1.1 jmmv /* Delete as many lines as possible from the bottom. */
188 1.1 jmmv available = oldy - 1 - s->cy;
189 1.1 jmmv if (available > 0) {
190 1.1 jmmv if (available > needed)
191 1.1 jmmv available = needed;
192 1.5.2.1 pgoyette grid_view_delete_lines(gd, oldy - available, available,
193 1.5.2.1 pgoyette 8);
194 1.1 jmmv }
195 1.1 jmmv needed -= available;
196 1.1 jmmv
197 1.1 jmmv /*
198 1.1 jmmv * Now just increase the history size, if possible, to take
199 1.1 jmmv * over the lines which are left. If history is off, delete
200 1.1 jmmv * lines from the top.
201 1.1 jmmv *
202 1.1 jmmv * XXX Should apply history limit?
203 1.1 jmmv */
204 1.1 jmmv available = s->cy;
205 1.5.2.1 pgoyette if (gd->flags & GRID_HISTORY) {
206 1.5.2.1 pgoyette gd->hscrolled += needed;
207 1.1 jmmv gd->hsize += needed;
208 1.5.2.1 pgoyette } else if (needed > 0 && available > 0) {
209 1.1 jmmv if (available > needed)
210 1.1 jmmv available = needed;
211 1.5.2.1 pgoyette grid_view_delete_lines(gd, 0, available, 8);
212 1.1 jmmv }
213 1.1 jmmv s->cy -= needed;
214 1.1 jmmv }
215 1.1 jmmv
216 1.1 jmmv /* Resize line arrays. */
217 1.5 christos gd->linedata = xreallocarray(gd->linedata, gd->hsize + sy,
218 1.5 christos sizeof *gd->linedata);
219 1.1 jmmv
220 1.1 jmmv /* Size increasing. */
221 1.1 jmmv if (sy > oldy) {
222 1.1 jmmv needed = sy - oldy;
223 1.1 jmmv
224 1.1 jmmv /*
225 1.5.2.1 pgoyette * Try to pull as much as possible out of scrolled history, if
226 1.5.2.1 pgoyette * is is enabled.
227 1.1 jmmv */
228 1.5.2.1 pgoyette available = gd->hscrolled;
229 1.1 jmmv if (gd->flags & GRID_HISTORY && available > 0) {
230 1.1 jmmv if (available > needed)
231 1.1 jmmv available = needed;
232 1.5.2.1 pgoyette gd->hscrolled -= available;
233 1.1 jmmv gd->hsize -= available;
234 1.1 jmmv s->cy += available;
235 1.1 jmmv } else
236 1.1 jmmv available = 0;
237 1.1 jmmv needed -= available;
238 1.1 jmmv
239 1.1 jmmv /* Then fill the rest in with blanks. */
240 1.1 jmmv for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++)
241 1.1 jmmv memset(&gd->linedata[i], 0, sizeof gd->linedata[i]);
242 1.1 jmmv }
243 1.1 jmmv
244 1.1 jmmv /* Set the new size, and reset the scroll region. */
245 1.1 jmmv gd->sy = sy;
246 1.1 jmmv s->rupper = 0;
247 1.1 jmmv s->rlower = screen_size_y(s) - 1;
248 1.1 jmmv }
249 1.1 jmmv
250 1.1 jmmv /* Set selection. */
251 1.1 jmmv void
252 1.1 jmmv screen_set_selection(struct screen *s, u_int sx, u_int sy,
253 1.1 jmmv u_int ex, u_int ey, u_int rectflag, struct grid_cell *gc)
254 1.1 jmmv {
255 1.1 jmmv struct screen_sel *sel = &s->sel;
256 1.1 jmmv
257 1.1 jmmv memcpy(&sel->cell, gc, sizeof sel->cell);
258 1.1 jmmv sel->flag = 1;
259 1.5.2.1 pgoyette sel->hidden = 0;
260 1.5.2.1 pgoyette
261 1.1 jmmv sel->rectflag = rectflag;
262 1.1 jmmv
263 1.1 jmmv sel->sx = sx; sel->sy = sy;
264 1.1 jmmv sel->ex = ex; sel->ey = ey;
265 1.1 jmmv }
266 1.1 jmmv
267 1.1 jmmv /* Clear selection. */
268 1.1 jmmv void
269 1.1 jmmv screen_clear_selection(struct screen *s)
270 1.1 jmmv {
271 1.1 jmmv struct screen_sel *sel = &s->sel;
272 1.1 jmmv
273 1.1 jmmv sel->flag = 0;
274 1.5.2.1 pgoyette sel->hidden = 0;
275 1.5 christos sel->lineflag = LINE_SEL_NONE;
276 1.1 jmmv }
277 1.1 jmmv
278 1.5.2.1 pgoyette /* Hide selection. */
279 1.5.2.1 pgoyette void
280 1.5.2.1 pgoyette screen_hide_selection(struct screen *s)
281 1.5.2.1 pgoyette {
282 1.5.2.1 pgoyette struct screen_sel *sel = &s->sel;
283 1.5.2.1 pgoyette
284 1.5.2.1 pgoyette sel->hidden = 1;
285 1.5.2.1 pgoyette }
286 1.5.2.1 pgoyette
287 1.1 jmmv /* Check if cell in selection. */
288 1.1 jmmv int
289 1.1 jmmv screen_check_selection(struct screen *s, u_int px, u_int py)
290 1.1 jmmv {
291 1.1 jmmv struct screen_sel *sel = &s->sel;
292 1.5 christos u_int xx;
293 1.1 jmmv
294 1.5.2.1 pgoyette if (!sel->flag || sel->hidden)
295 1.1 jmmv return (0);
296 1.1 jmmv
297 1.1 jmmv if (sel->rectflag) {
298 1.1 jmmv if (sel->sy < sel->ey) {
299 1.1 jmmv /* start line < end line -- downward selection. */
300 1.1 jmmv if (py < sel->sy || py > sel->ey)
301 1.1 jmmv return (0);
302 1.1 jmmv } else if (sel->sy > sel->ey) {
303 1.1 jmmv /* start line > end line -- upward selection. */
304 1.1 jmmv if (py > sel->sy || py < sel->ey)
305 1.1 jmmv return (0);
306 1.1 jmmv } else {
307 1.1 jmmv /* starting line == ending line. */
308 1.1 jmmv if (py != sel->sy)
309 1.1 jmmv return (0);
310 1.1 jmmv }
311 1.1 jmmv
312 1.1 jmmv /*
313 1.1 jmmv * Need to include the selection start row, but not the cursor
314 1.1 jmmv * row, which means the selection changes depending on which
315 1.1 jmmv * one is on the left.
316 1.1 jmmv */
317 1.1 jmmv if (sel->ex < sel->sx) {
318 1.1 jmmv /* Cursor (ex) is on the left. */
319 1.1 jmmv if (px < sel->ex)
320 1.1 jmmv return (0);
321 1.1 jmmv
322 1.1 jmmv if (px > sel->sx)
323 1.1 jmmv return (0);
324 1.1 jmmv } else {
325 1.1 jmmv /* Selection start (sx) is on the left. */
326 1.1 jmmv if (px < sel->sx)
327 1.1 jmmv return (0);
328 1.1 jmmv
329 1.1 jmmv if (px > sel->ex)
330 1.1 jmmv return (0);
331 1.1 jmmv }
332 1.1 jmmv } else {
333 1.1 jmmv /*
334 1.1 jmmv * Like emacs, keep the top-left-most character, and drop the
335 1.1 jmmv * bottom-right-most, regardless of copy direction.
336 1.1 jmmv */
337 1.1 jmmv if (sel->sy < sel->ey) {
338 1.1 jmmv /* starting line < ending line -- downward selection. */
339 1.1 jmmv if (py < sel->sy || py > sel->ey)
340 1.1 jmmv return (0);
341 1.1 jmmv
342 1.5 christos if (py == sel->sy && px < sel->sx)
343 1.5 christos return (0);
344 1.5 christos
345 1.5 christos if (py == sel->ey && px > sel->ex)
346 1.1 jmmv return (0);
347 1.1 jmmv } else if (sel->sy > sel->ey) {
348 1.1 jmmv /* starting line > ending line -- upward selection. */
349 1.1 jmmv if (py > sel->sy || py < sel->ey)
350 1.1 jmmv return (0);
351 1.1 jmmv
352 1.5 christos if (py == sel->ey && px < sel->ex)
353 1.5 christos return (0);
354 1.5 christos
355 1.5 christos if (sel->modekeys == MODEKEY_EMACS)
356 1.5 christos xx = sel->sx - 1;
357 1.5 christos else
358 1.5 christos xx = sel->sx;
359 1.5.2.1 pgoyette if (py == sel->sy && (sel->sx == 0 || px > xx))
360 1.1 jmmv return (0);
361 1.1 jmmv } else {
362 1.1 jmmv /* starting line == ending line. */
363 1.1 jmmv if (py != sel->sy)
364 1.1 jmmv return (0);
365 1.1 jmmv
366 1.1 jmmv if (sel->ex < sel->sx) {
367 1.1 jmmv /* cursor (ex) is on the left */
368 1.5 christos if (sel->modekeys == MODEKEY_EMACS)
369 1.5 christos xx = sel->sx - 1;
370 1.5 christos else
371 1.5 christos xx = sel->sx;
372 1.5 christos if (px > xx || px < sel->ex)
373 1.1 jmmv return (0);
374 1.1 jmmv } else {
375 1.1 jmmv /* selection start (sx) is on the left */
376 1.1 jmmv if (px < sel->sx || px > sel->ex)
377 1.1 jmmv return (0);
378 1.1 jmmv }
379 1.1 jmmv }
380 1.1 jmmv }
381 1.1 jmmv
382 1.1 jmmv return (1);
383 1.1 jmmv }
384 1.4 christos
385 1.5.2.1 pgoyette /* Get selected grid cell. */
386 1.4 christos void
387 1.5.2.1 pgoyette screen_select_cell(struct screen *s, struct grid_cell *dst,
388 1.5.2.1 pgoyette const struct grid_cell *src)
389 1.5.2.1 pgoyette {
390 1.5.2.1 pgoyette if (!s->sel.flag || s->sel.hidden)
391 1.5.2.1 pgoyette return;
392 1.5.2.1 pgoyette
393 1.5.2.1 pgoyette memcpy(dst, &s->sel.cell, sizeof *dst);
394 1.5.2.1 pgoyette
395 1.5.2.1 pgoyette utf8_copy(&dst->data, &src->data);
396 1.5.2.1 pgoyette dst->attr = dst->attr & ~GRID_ATTR_CHARSET;
397 1.5.2.1 pgoyette dst->attr |= src->attr & GRID_ATTR_CHARSET;
398 1.5.2.1 pgoyette dst->flags = src->flags;
399 1.5.2.1 pgoyette }
400 1.5.2.1 pgoyette
401 1.5.2.1 pgoyette /* Reflow wrapped lines. */
402 1.5.2.1 pgoyette static void
403 1.4 christos screen_reflow(struct screen *s, u_int new_x)
404 1.4 christos {
405 1.4 christos struct grid *old = s->grid;
406 1.4 christos u_int change;
407 1.4 christos
408 1.4 christos s->grid = grid_create(old->sx, old->sy, old->hlimit);
409 1.4 christos
410 1.4 christos change = grid_reflow(s->grid, old, new_x);
411 1.4 christos if (change < s->cy)
412 1.4 christos s->cy -= change;
413 1.4 christos else
414 1.4 christos s->cy = 0;
415 1.4 christos }
416