4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
27 /* Selected area in screen. */
39 struct grid_cell cell
;
42 /* Entry on title stack. */
43 struct screen_title_entry
{
46 TAILQ_ENTRY(screen_title_entry
) entry
;
48 TAILQ_HEAD(screen_titles
, screen_title_entry
);
50 static void screen_resize_y(struct screen
*, u_int
, int, u_int
*);
51 static void screen_reflow(struct screen
*, u_int
, u_int
*, u_int
*, int);
53 /* Free titles stack. */
55 screen_free_titles(struct screen
*s
)
57 struct screen_title_entry
*title_entry
;
59 if (s
->titles
== NULL
)
62 while ((title_entry
= TAILQ_FIRST(s
->titles
)) != NULL
) {
63 TAILQ_REMOVE(s
->titles
, title_entry
, entry
);
64 free(title_entry
->text
);
72 /* Create a new screen. */
74 screen_init(struct screen
*s
, u_int sx
, u_int sy
, u_int hlimit
)
76 s
->grid
= grid_create(sx
, sy
, hlimit
);
79 s
->title
= xstrdup("");
83 s
->cstyle
= SCREEN_CURSOR_DEFAULT
;
84 s
->default_cstyle
= SCREEN_CURSOR_DEFAULT
;
85 s
->mode
= MODE_CURSOR
;
88 s
->default_ccolour
= -1;
93 TAILQ_INIT(&s
->images
);
102 /* Reinitialise screen. */
104 screen_reinit(struct screen
*s
)
110 s
->rlower
= screen_size_y(s
) - 1;
112 s
->mode
= MODE_CURSOR
|MODE_WRAP
|(s
->mode
& MODE_CRLF
);
114 if (options_get_number(global_options
, "extended-keys") == 2)
115 s
->mode
= (s
->mode
& ~EXTENDED_KEY_MODES
)|MODE_KEYS_EXTENDED
;
117 if (SCREEN_IS_ALTERNATE(s
))
118 screen_alternate_off(s
, NULL
, 0);
119 s
->saved_cx
= UINT_MAX
;
120 s
->saved_cy
= UINT_MAX
;
122 screen_reset_tabs(s
);
124 grid_clear_lines(s
->grid
, s
->grid
->hsize
, s
->grid
->sy
, 8);
126 screen_clear_selection(s
);
127 screen_free_titles(s
);
133 screen_reset_hyperlinks(s
);
136 /* Reset hyperlinks of a screen. */
138 screen_reset_hyperlinks(struct screen
*s
)
140 if (s
->hyperlinks
== NULL
)
141 s
->hyperlinks
= hyperlinks_init();
143 hyperlinks_reset(s
->hyperlinks
);
146 /* Destroy a screen. */
148 screen_free(struct screen
*s
)
155 if (s
->write_list
!= NULL
)
156 screen_write_free_list(s
);
158 if (SCREEN_IS_ALTERNATE(s
))
159 grid_destroy(s
->saved_grid
);
160 grid_destroy(s
->grid
);
162 if (s
->hyperlinks
!= NULL
)
163 hyperlinks_free(s
->hyperlinks
);
164 screen_free_titles(s
);
171 /* Reset tabs to default, eight spaces apart. */
173 screen_reset_tabs(struct screen
*s
)
179 if ((s
->tabs
= bit_alloc(screen_size_x(s
))) == NULL
)
180 fatal("bit_alloc failed");
181 for (i
= 8; i
< screen_size_x(s
); i
+= 8)
185 /* Set default cursor style and colour from options. */
187 screen_set_default_cursor(struct screen
*s
, struct options
*oo
)
191 c
= options_get_number(oo
, "cursor-colour");
192 s
->default_ccolour
= c
;
194 c
= options_get_number(oo
, "cursor-style");
196 screen_set_cursor_style(c
, &s
->default_cstyle
, &s
->default_mode
);
199 /* Set screen cursor style and mode. */
201 screen_set_cursor_style(u_int style
, enum screen_cursor_style
*cstyle
,
206 *cstyle
= SCREEN_CURSOR_DEFAULT
;
209 *cstyle
= SCREEN_CURSOR_BLOCK
;
210 *mode
|= MODE_CURSOR_BLINKING
;
213 *cstyle
= SCREEN_CURSOR_BLOCK
;
214 *mode
&= ~MODE_CURSOR_BLINKING
;
217 *cstyle
= SCREEN_CURSOR_UNDERLINE
;
218 *mode
|= MODE_CURSOR_BLINKING
;
221 *cstyle
= SCREEN_CURSOR_UNDERLINE
;
222 *mode
&= ~MODE_CURSOR_BLINKING
;
225 *cstyle
= SCREEN_CURSOR_BAR
;
226 *mode
|= MODE_CURSOR_BLINKING
;
229 *cstyle
= SCREEN_CURSOR_BAR
;
230 *mode
&= ~MODE_CURSOR_BLINKING
;
235 /* Set screen cursor colour. */
237 screen_set_cursor_colour(struct screen
*s
, int colour
)
242 /* Set screen title. */
244 screen_set_title(struct screen
*s
, const char *title
)
246 if (!utf8_isvalid(title
))
249 s
->title
= xstrdup(title
);
253 /* Set screen path. */
255 screen_set_path(struct screen
*s
, const char *path
)
258 utf8_stravis(&s
->path
, path
, VIS_OCTAL
|VIS_CSTYLE
|VIS_TAB
|VIS_NL
);
261 /* Push the current title onto the stack. */
263 screen_push_title(struct screen
*s
)
265 struct screen_title_entry
*title_entry
;
267 if (s
->titles
== NULL
) {
268 s
->titles
= xmalloc(sizeof *s
->titles
);
269 TAILQ_INIT(s
->titles
);
271 title_entry
= xmalloc(sizeof *title_entry
);
272 title_entry
->text
= xstrdup(s
->title
);
273 TAILQ_INSERT_HEAD(s
->titles
, title_entry
, entry
);
277 * Pop a title from the stack and set it as the screen title. If the stack is
281 screen_pop_title(struct screen
*s
)
283 struct screen_title_entry
*title_entry
;
285 if (s
->titles
== NULL
)
288 title_entry
= TAILQ_FIRST(s
->titles
);
289 if (title_entry
!= NULL
) {
290 screen_set_title(s
, title_entry
->text
);
292 TAILQ_REMOVE(s
->titles
, title_entry
, entry
);
293 free(title_entry
->text
);
298 /* Resize screen with options. */
300 screen_resize_cursor(struct screen
*s
, u_int sx
, u_int sy
, int reflow
,
301 int eat_empty
, int cursor
)
303 u_int cx
= s
->cx
, cy
= s
->grid
->hsize
+ s
->cy
;
305 if (s
->write_list
!= NULL
)
306 screen_write_free_list(s
);
308 log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)",
309 __func__
, sx
, sy
, screen_size_x(s
), screen_size_y(s
), s
->cx
, s
->cy
,
317 if (sx
!= screen_size_x(s
)) {
319 screen_reset_tabs(s
);
323 if (sy
!= screen_size_y(s
))
324 screen_resize_y(s
, sy
, eat_empty
, &cy
);
331 screen_reflow(s
, sx
, &cx
, &cy
, cursor
);
333 if (cy
>= s
->grid
->hsize
) {
335 s
->cy
= cy
- s
->grid
->hsize
;
341 log_debug("%s: cursor finished at %u,%u = %u,%u", __func__
, s
->cx
,
344 if (s
->write_list
!= NULL
)
345 screen_write_make_list(s
);
350 screen_resize(struct screen
*s
, u_int sx
, u_int sy
, int reflow
)
352 screen_resize_cursor(s
, sx
, sy
, reflow
, 1, 1);
356 screen_resize_y(struct screen
*s
, u_int sy
, int eat_empty
, u_int
*cy
)
358 struct grid
*gd
= s
->grid
;
359 u_int needed
, available
, oldy
, i
;
363 oldy
= screen_size_y(s
);
368 * If the height is decreasing, delete lines from the bottom until
369 * hitting the cursor, then push lines from the top into the history.
371 * When increasing, pull as many lines as possible from scrolled
372 * history (not explicitly cleared from view) to the top, then fill the
373 * remaining with blanks at the bottom.
376 /* Size decreasing. */
380 /* Delete as many lines as possible from the bottom. */
382 available
= oldy
- 1 - s
->cy
;
384 if (available
> needed
)
386 grid_view_delete_lines(gd
, oldy
- available
,
393 * Now just increase the history size, if possible, to take
394 * over the lines which are left. If history is off, delete
395 * lines from the top.
398 if (gd
->flags
& GRID_HISTORY
) {
399 gd
->hscrolled
+= needed
;
401 } else if (needed
> 0 && available
> 0) {
402 if (available
> needed
)
404 grid_view_delete_lines(gd
, 0, available
, 8);
409 /* Resize line array. */
410 grid_adjust_lines(gd
, gd
->hsize
+ sy
);
412 /* Size increasing. */
417 * Try to pull as much as possible out of scrolled history, if
420 available
= gd
->hscrolled
;
421 if (gd
->flags
& GRID_HISTORY
&& available
> 0) {
422 if (available
> needed
)
424 gd
->hscrolled
-= available
;
425 gd
->hsize
-= available
;
430 /* Then fill the rest in with blanks. */
431 for (i
= gd
->hsize
+ sy
- needed
; i
< gd
->hsize
+ sy
; i
++)
432 grid_empty_line(gd
, i
, 8);
435 /* Set the new size, and reset the scroll region. */
438 s
->rlower
= screen_size_y(s
) - 1;
443 screen_set_selection(struct screen
*s
, u_int sx
, u_int sy
,
444 u_int ex
, u_int ey
, u_int rectangle
, int modekeys
, struct grid_cell
*gc
)
447 s
->sel
= xcalloc(1, sizeof *s
->sel
);
449 memcpy(&s
->sel
->cell
, gc
, sizeof s
->sel
->cell
);
451 s
->sel
->rectangle
= rectangle
;
452 s
->sel
->modekeys
= modekeys
;
460 /* Clear selection. */
462 screen_clear_selection(struct screen
*s
)
468 /* Hide selection. */
470 screen_hide_selection(struct screen
*s
)
476 /* Check if cell in selection. */
478 screen_check_selection(struct screen
*s
, u_int px
, u_int py
)
480 struct screen_sel
*sel
= s
->sel
;
483 if (sel
== NULL
|| sel
->hidden
)
486 if (sel
->rectangle
) {
487 if (sel
->sy
< sel
->ey
) {
488 /* start line < end line -- downward selection. */
489 if (py
< sel
->sy
|| py
> sel
->ey
)
491 } else if (sel
->sy
> sel
->ey
) {
492 /* start line > end line -- upward selection. */
493 if (py
> sel
->sy
|| py
< sel
->ey
)
496 /* starting line == ending line. */
502 * Need to include the selection start row, but not the cursor
503 * row, which means the selection changes depending on which
504 * one is on the left.
506 if (sel
->ex
< sel
->sx
) {
507 /* Cursor (ex) is on the left. */
514 /* Selection start (sx) is on the left. */
523 * Like emacs, keep the top-left-most character, and drop the
524 * bottom-right-most, regardless of copy direction.
526 if (sel
->sy
< sel
->ey
) {
527 /* starting line < ending line -- downward selection. */
528 if (py
< sel
->sy
|| py
> sel
->ey
)
531 if (py
== sel
->sy
&& px
< sel
->sx
)
534 if (sel
->modekeys
== MODEKEY_EMACS
)
535 xx
= (sel
->ex
== 0 ? 0 : sel
->ex
- 1);
538 if (py
== sel
->ey
&& px
> xx
)
540 } else if (sel
->sy
> sel
->ey
) {
541 /* starting line > ending line -- upward selection. */
542 if (py
> sel
->sy
|| py
< sel
->ey
)
545 if (py
== sel
->ey
&& px
< sel
->ex
)
548 if (sel
->modekeys
== MODEKEY_EMACS
)
552 if (py
== sel
->sy
&& (sel
->sx
== 0 || px
> xx
))
555 /* starting line == ending line. */
559 if (sel
->ex
< sel
->sx
) {
560 /* cursor (ex) is on the left */
561 if (sel
->modekeys
== MODEKEY_EMACS
)
565 if (px
> xx
|| px
< sel
->ex
)
568 /* selection start (sx) is on the left */
569 if (sel
->modekeys
== MODEKEY_EMACS
)
570 xx
= (sel
->ex
== 0 ? 0 : sel
->ex
- 1);
573 if (px
< sel
->sx
|| px
> xx
)
582 /* Get selected grid cell. */
584 screen_select_cell(struct screen
*s
, struct grid_cell
*dst
,
585 const struct grid_cell
*src
)
587 if (s
->sel
== NULL
|| s
->sel
->hidden
)
590 memcpy(dst
, &s
->sel
->cell
, sizeof *dst
);
592 utf8_copy(&dst
->data
, &src
->data
);
593 dst
->attr
= dst
->attr
& ~GRID_ATTR_CHARSET
;
594 dst
->attr
|= src
->attr
& GRID_ATTR_CHARSET
;
595 dst
->flags
= src
->flags
;
598 /* Reflow wrapped lines. */
600 screen_reflow(struct screen
*s
, u_int new_x
, u_int
*cx
, u_int
*cy
, int cursor
)
605 grid_wrap_position(s
->grid
, *cx
, *cy
, &wx
, &wy
);
606 log_debug("%s: cursor %u,%u is %u,%u", __func__
, *cx
, *cy
, wx
,
610 grid_reflow(s
->grid
, new_x
);
613 grid_unwrap_position(s
->grid
, cx
, cy
, wx
, wy
);
614 log_debug("%s: new cursor is %u,%u", __func__
, *cx
, *cy
);
618 *cy
= s
->grid
->hsize
;
623 * Enter alternative screen mode. A copy of the visible screen is saved and the
624 * history is not updated.
627 screen_alternate_on(struct screen
*s
, struct grid_cell
*gc
, int cursor
)
631 if (SCREEN_IS_ALTERNATE(s
))
633 sx
= screen_size_x(s
);
634 sy
= screen_size_y(s
);
636 s
->saved_grid
= grid_create(sx
, sy
, 0);
637 grid_duplicate_lines(s
->saved_grid
, 0, s
->grid
, screen_hsize(s
), sy
);
642 memcpy(&s
->saved_cell
, gc
, sizeof s
->saved_cell
);
644 grid_view_clear(s
->grid
, 0, 0, sx
, sy
, 8);
646 s
->saved_flags
= s
->grid
->flags
;
647 s
->grid
->flags
&= ~GRID_HISTORY
;
650 /* Exit alternate screen mode and restore the copied grid. */
652 screen_alternate_off(struct screen
*s
, struct grid_cell
*gc
, int cursor
)
654 u_int sx
= screen_size_x(s
), sy
= screen_size_y(s
);
657 * If the current size is different, temporarily resize to the old size
658 * before copying back.
660 if (SCREEN_IS_ALTERNATE(s
))
661 screen_resize(s
, s
->saved_grid
->sx
, s
->saved_grid
->sy
, 0);
664 * Restore the cursor position and cell. This happens even if not
665 * currently in the alternate screen.
667 if (cursor
&& s
->saved_cx
!= UINT_MAX
&& s
->saved_cy
!= UINT_MAX
) {
671 memcpy(gc
, &s
->saved_cell
, sizeof *gc
);
674 /* If not in the alternate screen, do nothing more. */
675 if (!SCREEN_IS_ALTERNATE(s
)) {
676 if (s
->cx
> screen_size_x(s
) - 1)
677 s
->cx
= screen_size_x(s
) - 1;
678 if (s
->cy
> screen_size_y(s
) - 1)
679 s
->cy
= screen_size_y(s
) - 1;
683 /* Restore the saved grid. */
684 grid_duplicate_lines(s
->grid
, screen_hsize(s
), s
->saved_grid
, 0,
688 * Turn history back on (so resize can use it) and then resize back to
691 if (s
->saved_flags
& GRID_HISTORY
)
692 s
->grid
->flags
|= GRID_HISTORY
;
693 screen_resize(s
, sx
, sy
, 1);
695 grid_destroy(s
->saved_grid
);
696 s
->saved_grid
= NULL
;
698 if (s
->cx
> screen_size_x(s
) - 1)
699 s
->cx
= screen_size_x(s
) - 1;
700 if (s
->cy
> screen_size_y(s
) - 1)
701 s
->cy
= screen_size_y(s
) - 1;
704 /* Get mode as a string. */
706 screen_mode_to_string(int mode
)
708 static char tmp
[1024];
712 if (mode
== ALL_MODES
)
716 if (mode
& MODE_CURSOR
)
717 strlcat(tmp
, "CURSOR,", sizeof tmp
);
718 if (mode
& MODE_INSERT
)
719 strlcat(tmp
, "INSERT,", sizeof tmp
);
720 if (mode
& MODE_KCURSOR
)
721 strlcat(tmp
, "KCURSOR,", sizeof tmp
);
722 if (mode
& MODE_KKEYPAD
)
723 strlcat(tmp
, "KKEYPAD,", sizeof tmp
);
724 if (mode
& MODE_WRAP
)
725 strlcat(tmp
, "WRAP,", sizeof tmp
);
726 if (mode
& MODE_MOUSE_STANDARD
)
727 strlcat(tmp
, "MOUSE_STANDARD,", sizeof tmp
);
728 if (mode
& MODE_MOUSE_BUTTON
)
729 strlcat(tmp
, "MOUSE_BUTTON,", sizeof tmp
);
730 if (mode
& MODE_CURSOR_BLINKING
)
731 strlcat(tmp
, "CURSOR_BLINKING,", sizeof tmp
);
732 if (mode
& MODE_CURSOR_VERY_VISIBLE
)
733 strlcat(tmp
, "CURSOR_VERY_VISIBLE,", sizeof tmp
);
734 if (mode
& MODE_MOUSE_UTF8
)
735 strlcat(tmp
, "MOUSE_UTF8,", sizeof tmp
);
736 if (mode
& MODE_MOUSE_SGR
)
737 strlcat(tmp
, "MOUSE_SGR,", sizeof tmp
);
738 if (mode
& MODE_BRACKETPASTE
)
739 strlcat(tmp
, "BRACKETPASTE,", sizeof tmp
);
740 if (mode
& MODE_FOCUSON
)
741 strlcat(tmp
, "FOCUSON,", sizeof tmp
);
742 if (mode
& MODE_MOUSE_ALL
)
743 strlcat(tmp
, "MOUSE_ALL,", sizeof tmp
);
744 if (mode
& MODE_ORIGIN
)
745 strlcat(tmp
, "ORIGIN,", sizeof tmp
);
746 if (mode
& MODE_CRLF
)
747 strlcat(tmp
, "CRLF,", sizeof tmp
);
748 if (mode
& MODE_KEYS_EXTENDED
)
749 strlcat(tmp
, "KEYS_EXTENDED,", sizeof tmp
);
750 if (mode
& MODE_KEYS_EXTENDED_2
)
751 strlcat(tmp
, "KEYS_EXTENDED_2,", sizeof tmp
);
752 tmp
[strlen(tmp
) - 1] = '\0';