Partly restore old refresh behavior
[tig.git] / src / display.c
blob347af8ad8f25d53e4cb60bded283658dfd563222
1 /* Copyright (c) 2006-2014 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/tig.h"
15 #include "tig/argv.h"
16 #include "tig/io.h"
17 #include "tig/repo.h"
18 #include "tig/options.h"
19 #include "tig/view.h"
20 #include "tig/draw.h"
21 #include "tig/display.h"
22 #include "tig/watch.h"
24 struct view *display[2];
25 unsigned int current_view;
27 static WINDOW *display_win[2];
28 static WINDOW *display_title[2];
29 static WINDOW *display_sep;
31 static FILE *opt_tty;
33 bool
34 open_external_viewer(const char *argv[], const char *dir, bool confirm, bool refresh, const char *notice)
36 bool ok;
38 def_prog_mode(); /* save current tty modes */
39 endwin(); /* restore original tty modes */
40 ok = io_run_fg(argv, dir);
41 if (confirm || !ok) {
42 if (!ok && *notice)
43 fprintf(stderr, "%s", notice);
44 if (!is_script_executing()) {
45 fprintf(stderr, "Press Enter to continue");
46 getc(opt_tty);
49 reset_prog_mode();
50 if (watch_update(WATCH_EVENT_AFTER_COMMAND) && refresh) {
51 struct view *view;
52 int i;
54 foreach_displayed_view (view, i) {
55 if (watch_dirty(&view->watch))
56 refresh_view(view);
59 redraw_display(TRUE);
60 return ok;
63 #define EDITOR_LINENO_MSG \
64 "*** Your editor reported an error while opening the file.\n" \
65 "*** This is probably because it doesn't support the line\n" \
66 "*** number argument added automatically. The line number\n" \
67 "*** has been disabled for now. You can permanently disable\n" \
68 "*** it by adding the following line to ~/.tigrc\n" \
69 "*** set editor-line-number = no\n"
71 void
72 open_editor(const char *file, unsigned int lineno)
74 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
75 char editor_cmd[SIZEOF_STR];
76 char lineno_cmd[SIZEOF_STR];
77 const char *editor;
78 int argc = 0;
80 editor = getenv("GIT_EDITOR");
81 if (!editor && *opt_editor)
82 editor = opt_editor;
83 if (!editor)
84 editor = getenv("VISUAL");
85 if (!editor)
86 editor = getenv("EDITOR");
87 if (!editor)
88 editor = "vi";
90 string_ncopy(editor_cmd, editor, strlen(editor));
91 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
92 report("Failed to read editor command");
93 return;
96 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
97 editor_argv[argc++] = lineno_cmd;
98 editor_argv[argc] = file;
99 if (!open_external_viewer(editor_argv, repo.cdup, FALSE, TRUE, EDITOR_LINENO_MSG))
100 opt_editor_line_number = FALSE;
104 static void
105 apply_horizontal_split(struct view *base, struct view *view)
107 view->width = base->width;
108 view->height = apply_step(opt_split_view_height, base->height);
109 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
110 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
111 base->height -= view->height;
114 static void
115 apply_vertical_split(struct view *base, struct view *view)
117 view->height = base->height;
118 view->width = apply_step(VSPLIT_SCALE, base->width);
119 view->width = MAX(view->width, MIN_VIEW_WIDTH);
120 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
121 base->width -= view->width;
124 static bool
125 vertical_split_is_enabled(void)
127 if (opt_vertical_split == VERTICAL_SPLIT_AUTO) {
128 int height, width;
130 getmaxyx(stdscr, height, width);
131 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
134 return opt_vertical_split == VERTICAL_SPLIT_VERTICAL;
137 static void
138 redraw_display_separator(bool clear)
140 if (displayed_views() > 1 && vertical_split_is_enabled()) {
141 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
143 if (clear)
144 wclear(display_sep);
145 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
146 wnoutrefresh(display_sep);
150 void
151 resize_display(void)
153 int x, y, i;
154 struct view *base = display[0];
155 struct view *view = display[1] ? display[1] : display[0];
156 bool vsplit;
158 /* Setup window dimensions */
160 getmaxyx(stdscr, base->height, base->width);
162 /* Make room for the status window. */
163 base->height -= 1;
165 vsplit = vertical_split_is_enabled();
167 if (view != base) {
168 if (vsplit) {
169 apply_vertical_split(base, view);
171 /* Make room for the separator bar. */
172 view->width -= 1;
173 } else {
174 apply_horizontal_split(base, view);
177 /* Make room for the title bar. */
178 view->height -= 1;
181 string_format(opt_env_columns, "COLUMNS=%d", base->width);
182 string_format(opt_env_lines, "LINES=%d", base->height);
184 /* Make room for the title bar. */
185 base->height -= 1;
187 x = y = 0;
189 foreach_displayed_view (view, i) {
190 if (!display_win[i]) {
191 display_win[i] = newwin(view->height, view->width, y, x);
192 if (!display_win[i])
193 die("Failed to create %s view", view->name);
195 scrollok(display_win[i], FALSE);
197 display_title[i] = newwin(1, view->width, y + view->height, x);
198 if (!display_title[i])
199 die("Failed to create title window");
201 } else {
202 wresize(display_win[i], view->height, view->width);
203 mvwin(display_win[i], y, x);
204 wresize(display_title[i], 1, view->width);
205 mvwin(display_title[i], y + view->height, x);
208 if (i > 0 && vsplit) {
209 if (!display_sep) {
210 display_sep = newwin(view->height, 1, 0, x - 1);
211 if (!display_sep)
212 die("Failed to create separator window");
214 } else {
215 wresize(display_sep, view->height, 1);
216 mvwin(display_sep, 0, x - 1);
220 view->win = display_win[i];
221 view->title = display_title[i];
223 if (vsplit)
224 x += view->width + 1;
225 else
226 y += view->height + 1;
229 redraw_display_separator(FALSE);
232 void
233 redraw_display(bool clear)
235 struct view *view;
236 int i;
238 foreach_displayed_view (view, i) {
239 if (clear)
240 wclear(view->win);
241 redraw_view(view);
242 update_view_title(view);
245 redraw_display_separator(clear);
248 static bool
249 save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize)
251 int read = mvwinnstr(win, y, 0, buf, bufsize);
253 return read == ERR ? FALSE : fprintf(file, "%s\n", buf) == read + 1;
256 static bool
257 save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize)
259 int read1 = mvwinnstr(left, y, 0, buf, bufsize);
260 int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1);
262 if (read2 == ERR)
263 return FALSE;
264 buf[read1] = '|';
266 return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1;
269 bool
270 save_display(const char *path)
272 int i, width;
273 char *line;
274 FILE *file = fopen(path, "w");
275 bool ok = TRUE;
276 struct view *view = display[0];
278 if (!file)
279 return FALSE;
281 getmaxyx(stdscr, i, width);
282 line = malloc(width + 1);
283 if (!line) {
284 fclose(file);
285 return FALSE;
288 if (view->width < width) {
289 struct view *left = display[0],
290 *right = display[1];
292 for (i = 0; ok && i < left->height; i++)
293 ok = save_window_vline(file, left->win, right->win, i, line, width);
294 if (ok)
295 ok = save_window_vline(file, left->title, right->title, 0, line, width);
296 } else {
297 int j;
299 foreach_displayed_view (view, j) {
300 for (i = 0; ok && i < view->height; i++)
301 ok = save_window_line(file, view->win, i, line, width);
302 if (ok)
303 ok = save_window_line(file, view->title, 0, line, width);
307 free(line);
308 fclose(file);
309 return ok;
313 * Status management
316 /* Whether or not the curses interface has been initialized. */
317 static bool cursed = FALSE;
319 /* Terminal hacks and workarounds. */
320 static bool use_scroll_redrawwin;
321 static bool use_scroll_status_wclear;
323 /* The status window is used for polling keystrokes. */
324 WINDOW *status_win;
326 /* Reading from the prompt? */
327 static bool input_mode = FALSE;
329 static bool status_empty = FALSE;
331 /* Update status and title window. */
332 static bool
333 update_status_window(struct view *view, const char *msg, va_list args)
335 if (input_mode)
336 return FALSE;
338 if (!status_empty || *msg) {
339 wmove(status_win, 0, 0);
340 if (view && view->has_scrolled && use_scroll_status_wclear)
341 wclear(status_win);
342 if (*msg) {
343 vwprintw(status_win, msg, args);
344 status_empty = FALSE;
345 } else {
346 status_empty = TRUE;
348 wclrtoeol(status_win);
349 return TRUE;
352 return FALSE;
355 void
356 update_status(const char *msg, ...)
358 va_list args;
360 va_start(args, msg);
361 update_status_window(display[current_view], msg, args);
362 va_end(args);
365 void
366 report(const char *msg, ...)
368 struct view *view = display[current_view];
369 va_list args;
371 if (!view) {
372 char buf[SIZEOF_STR];
373 int retval;
375 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, TRUE);
376 die("%s", buf);
379 va_start(args, msg);
380 if (update_status_window(view, msg, args))
381 wnoutrefresh(status_win);
382 va_end(args);
384 update_view_title(view);
387 static void
388 done_display(void)
390 endwin();
393 void
394 init_display(void)
396 bool no_display = !!getenv("TIG_NO_DISPLAY");
397 const char *term;
398 int x, y;
400 die_callback = done_display;
401 /* XXX: Restore tty modes and let the OS cleanup the rest! */
402 if (atexit(done_display))
403 die("Failed to register done_display");
405 /* Initialize the curses library */
406 if (!no_display && isatty(STDIN_FILENO)) {
407 cursed = !!initscr();
408 opt_tty = stdin;
409 } else {
410 /* Leave stdin and stdout alone when acting as a pager. */
411 FILE *out_tty;
413 opt_tty = fopen("/dev/tty", "r+");
414 out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty;
415 if (!opt_tty || !out_tty)
416 die("Failed to open /dev/tty");
417 cursed = !!newterm(NULL, out_tty, opt_tty);
420 if (!cursed)
421 die("Failed to initialize curses");
423 nonl(); /* Disable conversion and detect newlines from input. */
424 cbreak(); /* Take input chars one at a time, no wait for \n */
425 noecho(); /* Don't echo input */
426 leaveok(stdscr, FALSE);
428 init_colors();
430 getmaxyx(stdscr, y, x);
431 status_win = newwin(1, x, y - 1, 0);
432 if (!status_win)
433 die("Failed to create status window");
435 /* Enable keyboard mapping */
436 keypad(status_win, TRUE);
437 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
438 enable_mouse(opt_mouse);
440 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
441 set_tabsize(opt_tab_size);
442 #else
443 TABSIZE = opt_tab_size;
444 #endif
446 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
447 if (term && !strcmp(term, "gnome-terminal")) {
448 /* In the gnome-terminal-emulator, the message from
449 * scrolling up one line when impossible followed by
450 * scrolling down one line causes corruption of the
451 * status line. This is fixed by calling wclear. */
452 use_scroll_status_wclear = TRUE;
453 use_scroll_redrawwin = FALSE;
455 } else if (term && !strcmp(term, "xrvt-xpm")) {
456 /* No problems with full optimizations in xrvt-(unicode)
457 * and aterm. */
458 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
460 } else {
461 /* When scrolling in (u)xterm the last line in the
462 * scrolling direction will update slowly. */
463 use_scroll_redrawwin = TRUE;
464 use_scroll_status_wclear = FALSE;
468 static struct io script_io = { -1 };
470 bool
471 open_script(const char *path)
473 return io_open(&script_io, "%s", path);
476 bool
477 is_script_executing(void)
479 return script_io.pipe != -1;
482 static bool
483 read_script(struct key *key, int delay)
485 static struct buffer input_buffer;
486 static const char *line = "";
487 enum status_code code;
489 if (!line || !*line) {
490 if (input_buffer.data && *input_buffer.data == ':') {
491 line = "<Enter>";
492 memset(&input_buffer, 0, sizeof(input_buffer));
494 } else if (!io_get(&script_io, &input_buffer, '\n', TRUE)) {
495 io_done(&script_io);
496 return FALSE;
497 } else {
498 line = input_buffer.data;
502 if (!strcmp(line, ":wait")) {
503 if (delay != 0)
504 line = input_buffer.data = NULL;
505 return FALSE;
508 code = get_key_value(&line, key);
509 if (code != SUCCESS)
510 die("Error reading script: %s", get_status_message(code));
511 return TRUE;
515 get_input_char(void)
517 if (is_script_executing()) {
518 static struct key key;
519 static int bytes_pos;
521 if (!key.modifiers.multibytes || bytes_pos >= strlen(key.data.bytes)) {
522 if (!read_script(&key, 0))
523 return 0;
524 bytes_pos = 0;
527 if (!key.modifiers.multibytes) {
528 if (key.data.value < 128)
529 return key.data.value;
530 die("Only ASCII control characters can be used in prompts: %d", key.data.value);
533 return key.data.bytes[bytes_pos++];
536 return getc(opt_tty);
540 get_input(int prompt_position, struct key *key, bool modifiers)
542 struct view *view;
543 int i, key_value, cursor_y, cursor_x;
545 if (prompt_position)
546 input_mode = TRUE;
548 memset(key, 0, sizeof(*key));
550 while (TRUE) {
551 int delay = -1;
553 if (opt_refresh_mode == REFRESH_MODE_PERIODIC) {
554 delay = watch_periodic(opt_refresh_interval);
555 foreach_displayed_view (view, i) {
556 if (view_can_refresh(view) &&
557 watch_dirty(&view->watch))
558 refresh_view(view);
562 foreach_view (view, i) {
563 update_view(view);
564 if (view_is_displayed(view) && view->has_scrolled &&
565 use_scroll_redrawwin)
566 redrawwin(view->win);
567 view->has_scrolled = FALSE;
568 if (view->pipe)
569 delay = 0;
572 /* Update the cursor position. */
573 if (prompt_position) {
574 getbegyx(status_win, cursor_y, cursor_x);
575 cursor_x = prompt_position;
576 } else {
577 view = display[current_view];
578 getbegyx(view->win, cursor_y, cursor_x);
579 cursor_x = view->width - 1;
580 cursor_y += view->pos.lineno - view->pos.offset;
582 setsyx(cursor_y, cursor_x);
584 if (is_script_executing()) {
585 if (!read_script(key, delay))
586 continue;
587 return key->modifiers.multibytes ? OK : key->data.value;
589 } else {
590 /* Refresh, accept single keystroke of input */
591 doupdate();
592 wtimeout(status_win, delay);
593 key_value = wgetch(status_win);
596 /* wgetch() with nodelay() enabled returns ERR when
597 * there's no input. */
598 if (key_value == ERR) {
600 } else if (key_value == KEY_ESC && modifiers) {
601 key->modifiers.escape = 1;
603 } else if (key_value == KEY_RESIZE) {
604 int height, width;
606 getmaxyx(stdscr, height, width);
608 wresize(status_win, 1, width);
609 mvwin(status_win, height - 1, 0);
610 wnoutrefresh(status_win);
611 resize_display();
612 redraw_display(TRUE);
614 } else {
615 int pos, key_length;
617 input_mode = FALSE;
618 if (key_value == erasechar())
619 key_value = KEY_BACKSPACE;
622 * Ctrl-<key> values are represented using a 0x1F
623 * bitmask on the key value. To 'unmap' we assume that:
625 * - Ctrl-Z is handled by Ncurses.
626 * - Ctrl-m is the same as Return/Enter.
627 * - Ctrl-i is the same as Tab.
629 * For all other key values in the range the Ctrl flag
630 * is set and the key value is updated to the proper
631 * ASCII value.
633 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
634 key_value != KEY_RETURN && key_value != KEY_TAB) {
635 key->modifiers.control = 1;
636 key_value = key_value | 0x40;
639 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
640 key->data.value = key_value;
641 return key->data.value;
644 key->modifiers.multibytes = 1;
645 key->data.bytes[0] = key_value;
647 key_length = utf8_char_length(key->data.bytes);
648 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
649 key->data.bytes[pos] = wgetch(status_win);
652 return OK;
657 void
658 enable_mouse(bool enable)
660 #ifdef NCURSES_MOUSE_VERSION
661 static bool enabled = FALSE;
663 if (enable != enabled) {
664 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
666 if (mousemask(mask, NULL))
667 mouseinterval(0);
668 enabled = enable;
670 #endif
673 /* vim: set ts=8 sw=8 noexpandtab: */