Unify view refresh checking
[tig.git] / src / display.c
blob080c0a8fcdeb627585a3b7feaa3b73c8709ee27a
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"
23 struct view *display[2];
24 unsigned int current_view;
26 static WINDOW *display_win[2];
27 static WINDOW *display_title[2];
28 static WINDOW *display_sep;
30 FILE *opt_tty;
32 bool
33 open_external_viewer(const char *argv[], const char *dir, bool confirm, const char *notice)
35 bool ok;
37 def_prog_mode(); /* save current tty modes */
38 endwin(); /* restore original tty modes */
39 ok = io_run_fg(argv, dir);
40 if (confirm || !ok) {
41 if (!ok && *notice)
42 fprintf(stderr, "%s", notice);
43 fprintf(stderr, "Press Enter to continue");
44 getc(opt_tty);
46 reset_prog_mode();
47 redraw_display(TRUE);
48 return ok;
51 #define EDITOR_LINENO_MSG \
52 "*** Your editor reported an error while opening the file.\n" \
53 "*** This is probably because it doesn't support the line\n" \
54 "*** number argument added automatically. The line number\n" \
55 "*** has been disabled for now. You can permanently disable\n" \
56 "*** it by adding the following line to ~/.tigrc\n" \
57 "*** set editor-line-number = no\n"
59 void
60 open_editor(const char *file, unsigned int lineno)
62 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
63 char editor_cmd[SIZEOF_STR];
64 char lineno_cmd[SIZEOF_STR];
65 const char *editor;
66 int argc = 0;
68 editor = getenv("GIT_EDITOR");
69 if (!editor && *opt_editor)
70 editor = opt_editor;
71 if (!editor)
72 editor = getenv("VISUAL");
73 if (!editor)
74 editor = getenv("EDITOR");
75 if (!editor)
76 editor = "vi";
78 string_ncopy(editor_cmd, editor, strlen(editor));
79 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
80 report("Failed to read editor command");
81 return;
84 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
85 editor_argv[argc++] = lineno_cmd;
86 editor_argv[argc] = file;
87 if (!open_external_viewer(editor_argv, repo.cdup, FALSE, EDITOR_LINENO_MSG))
88 opt_editor_line_number = FALSE;
92 static void
93 apply_horizontal_split(struct view *base, struct view *view)
95 view->width = base->width;
96 view->height = apply_step(opt_split_view_height, base->height);
97 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
98 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
99 base->height -= view->height;
102 static void
103 apply_vertical_split(struct view *base, struct view *view)
105 view->height = base->height;
106 view->width = apply_step(VSPLIT_SCALE, base->width);
107 view->width = MAX(view->width, MIN_VIEW_WIDTH);
108 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
109 base->width -= view->width;
112 static bool
113 vertical_split_is_enabled(void)
115 if (opt_vertical_split == VERTICAL_SPLIT_AUTO) {
116 int height, width;
118 getmaxyx(stdscr, height, width);
119 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
122 return opt_vertical_split == VERTICAL_SPLIT_VERTICAL;
125 static void
126 redraw_display_separator(bool clear)
128 if (displayed_views() > 1 && vertical_split_is_enabled()) {
129 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
131 if (clear)
132 wclear(display_sep);
133 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
134 wnoutrefresh(display_sep);
138 void
139 resize_display(void)
141 int x, y, i;
142 struct view *base = display[0];
143 struct view *view = display[1] ? display[1] : display[0];
144 bool vsplit;
146 /* Setup window dimensions */
148 getmaxyx(stdscr, base->height, base->width);
150 /* Make room for the status window. */
151 base->height -= 1;
153 vsplit = vertical_split_is_enabled();
155 if (view != base) {
156 if (vsplit) {
157 apply_vertical_split(base, view);
159 /* Make room for the separator bar. */
160 view->width -= 1;
161 } else {
162 apply_horizontal_split(base, view);
165 /* Make room for the title bar. */
166 view->height -= 1;
169 string_format(opt_env_columns, "COLUMNS=%d", base->width);
170 string_format(opt_env_lines, "LINES=%d", base->height);
172 /* Make room for the title bar. */
173 base->height -= 1;
175 x = y = 0;
177 foreach_displayed_view (view, i) {
178 if (!display_win[i]) {
179 display_win[i] = newwin(view->height, view->width, y, x);
180 if (!display_win[i])
181 die("Failed to create %s view", view->name);
183 scrollok(display_win[i], FALSE);
185 display_title[i] = newwin(1, view->width, y + view->height, x);
186 if (!display_title[i])
187 die("Failed to create title window");
189 } else {
190 wresize(display_win[i], view->height, view->width);
191 mvwin(display_win[i], y, x);
192 wresize(display_title[i], 1, view->width);
193 mvwin(display_title[i], y + view->height, x);
196 if (i > 0 && vsplit) {
197 if (!display_sep) {
198 display_sep = newwin(view->height, 1, 0, x - 1);
199 if (!display_sep)
200 die("Failed to create separator window");
202 } else {
203 wresize(display_sep, view->height, 1);
204 mvwin(display_sep, 0, x - 1);
208 view->win = display_win[i];
209 view->title = display_title[i];
211 if (vsplit)
212 x += view->width + 1;
213 else
214 y += view->height + 1;
217 redraw_display_separator(FALSE);
220 void
221 redraw_display(bool clear)
223 struct view *view;
224 int i;
226 foreach_displayed_view (view, i) {
227 if (clear)
228 wclear(view->win);
229 redraw_view(view);
230 update_view_title(view);
233 redraw_display_separator(clear);
237 * Status management
240 /* Whether or not the curses interface has been initialized. */
241 static bool cursed = FALSE;
243 /* Terminal hacks and workarounds. */
244 static bool use_scroll_redrawwin;
245 static bool use_scroll_status_wclear;
247 /* The status window is used for polling keystrokes. */
248 WINDOW *status_win;
250 /* Reading from the prompt? */
251 static bool input_mode = FALSE;
253 static bool status_empty = FALSE;
255 /* Update status and title window. */
256 static bool
257 update_status_window(struct view *view, const char *msg, va_list args)
259 if (input_mode)
260 return FALSE;
262 if (!status_empty || *msg) {
263 wmove(status_win, 0, 0);
264 if (view && view->has_scrolled && use_scroll_status_wclear)
265 wclear(status_win);
266 if (*msg) {
267 vwprintw(status_win, msg, args);
268 status_empty = FALSE;
269 } else {
270 status_empty = TRUE;
272 wclrtoeol(status_win);
273 return TRUE;
276 return FALSE;
279 void
280 update_status(const char *msg, ...)
282 va_list args;
284 va_start(args, msg);
285 update_status_window(display[current_view], msg, args);
286 va_end(args);
289 void
290 report(const char *msg, ...)
292 struct view *view = display[current_view];
293 va_list args;
295 if (!view) {
296 char buf[SIZEOF_STR];
297 int retval;
299 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, TRUE);
300 die("%s", buf);
303 va_start(args, msg);
304 if (update_status_window(view, msg, args))
305 wnoutrefresh(status_win);
306 va_end(args);
308 update_view_title(view);
311 static void
312 done_display(void)
314 endwin();
317 void
318 init_display(void)
320 const char *term;
321 int x, y;
323 die_callback = done_display;
324 /* XXX: Restore tty modes and let the OS cleanup the rest! */
325 if (atexit(done_display))
326 die("Failed to register done_display");
328 /* Initialize the curses library */
329 if (isatty(STDIN_FILENO)) {
330 cursed = !!initscr();
331 opt_tty = stdin;
332 } else {
333 /* Leave stdin and stdout alone when acting as a pager. */
334 opt_tty = fopen("/dev/tty", "r+");
335 if (!opt_tty)
336 die("Failed to open /dev/tty");
337 cursed = !!newterm(NULL, opt_tty, opt_tty);
340 if (!cursed)
341 die("Failed to initialize curses");
343 nonl(); /* Disable conversion and detect newlines from input. */
344 cbreak(); /* Take input chars one at a time, no wait for \n */
345 noecho(); /* Don't echo input */
346 leaveok(stdscr, FALSE);
348 if (has_colors())
349 init_colors();
351 getmaxyx(stdscr, y, x);
352 status_win = newwin(1, x, y - 1, 0);
353 if (!status_win)
354 die("Failed to create status window");
356 /* Enable keyboard mapping */
357 keypad(status_win, TRUE);
358 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
359 enable_mouse(opt_mouse);
361 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
362 set_tabsize(opt_tab_size);
363 #else
364 TABSIZE = opt_tab_size;
365 #endif
367 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
368 if (term && !strcmp(term, "gnome-terminal")) {
369 /* In the gnome-terminal-emulator, the message from
370 * scrolling up one line when impossible followed by
371 * scrolling down one line causes corruption of the
372 * status line. This is fixed by calling wclear. */
373 use_scroll_status_wclear = TRUE;
374 use_scroll_redrawwin = FALSE;
376 } else if (term && !strcmp(term, "xrvt-xpm")) {
377 /* No problems with full optimizations in xrvt-(unicode)
378 * and aterm. */
379 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
381 } else {
382 /* When scrolling in (u)xterm the last line in the
383 * scrolling direction will update slowly. */
384 use_scroll_redrawwin = TRUE;
385 use_scroll_status_wclear = FALSE;
390 get_input(int prompt_position, struct key *key, bool modifiers)
392 struct view *view;
393 int i, key_value, cursor_y, cursor_x;
395 if (prompt_position)
396 input_mode = TRUE;
398 memset(key, 0, sizeof(*key));
400 while (TRUE) {
401 bool loading = FALSE;
403 foreach_view (view, i) {
404 update_view(view);
405 if (view_is_displayed(view) && view->has_scrolled &&
406 use_scroll_redrawwin)
407 redrawwin(view->win);
408 view->has_scrolled = FALSE;
409 if (view->pipe)
410 loading = TRUE;
413 /* Update the cursor position. */
414 if (prompt_position) {
415 getbegyx(status_win, cursor_y, cursor_x);
416 cursor_x = prompt_position;
417 } else {
418 view = display[current_view];
419 getbegyx(view->win, cursor_y, cursor_x);
420 cursor_x = view->width - 1;
421 cursor_y += view->pos.lineno - view->pos.offset;
423 setsyx(cursor_y, cursor_x);
425 /* Refresh, accept single keystroke of input */
426 doupdate();
427 nodelay(status_win, loading);
428 key_value = wgetch(status_win);
430 /* wgetch() with nodelay() enabled returns ERR when
431 * there's no input. */
432 if (key_value == ERR) {
434 } else if (key_value == KEY_ESC && modifiers) {
435 key->modifiers.escape = 1;
437 } else if (key_value == KEY_RESIZE) {
438 int height, width;
440 getmaxyx(stdscr, height, width);
442 wresize(status_win, 1, width);
443 mvwin(status_win, height - 1, 0);
444 wnoutrefresh(status_win);
445 resize_display();
446 redraw_display(TRUE);
448 } else {
449 int pos, key_length;
451 input_mode = FALSE;
452 if (key_value == erasechar())
453 key_value = KEY_BACKSPACE;
456 * Ctrl-<key> values are represented using a 0x1F
457 * bitmask on the key value. To 'unmap' we assume that:
459 * - Ctrl-Z is handled by Ncurses.
460 * - Ctrl-m is the same as Return/Enter.
461 * - Ctrl-i is the same as Tab.
463 * For all other key values in the range the Ctrl flag
464 * is set and the key value is updated to the proper
465 * ASCII value.
467 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
468 key_value != KEY_RETURN && key_value != KEY_TAB) {
469 key->modifiers.control = 1;
470 key_value = key_value | 0x40;
473 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
474 key->data.value = key_value;
475 return key->data.value;
478 key->modifiers.multibytes = 1;
479 key->data.bytes[0] = key_value;
481 key_length = utf8_char_length(key->data.bytes);
482 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
483 key->data.bytes[pos] = wgetch(status_win);
486 return OK;
491 void
492 enable_mouse(bool enable)
494 #ifdef NCURSES_MOUSE_VERSION
495 static bool enabled = FALSE;
497 if (enable != enabled) {
498 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
500 if (mousemask(mask, NULL))
501 mouseinterval(0);
502 enabled = enable;
504 #endif
507 /* vim: set ts=8 sw=8 noexpandtab: */