Add test for TIGRC_USER and TIGRC_SYSTEM env variables
[tig.git] / src / display.c
blobe3ba34e4f7981fd1dbe16bf291d2a87dd52c140c
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 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 fprintf(stderr, "Press Enter to continue");
45 getc(opt_tty);
47 reset_prog_mode();
48 if (watch_update(WATCH_EVENT_AFTER_EXTERNAL) && refresh) {
49 struct view *view;
50 int i;
52 foreach_displayed_view (view, i) {
53 if (watch_dirty(&view->watch))
54 refresh_view(view);
57 redraw_display(TRUE);
58 return ok;
61 #define EDITOR_LINENO_MSG \
62 "*** Your editor reported an error while opening the file.\n" \
63 "*** This is probably because it doesn't support the line\n" \
64 "*** number argument added automatically. The line number\n" \
65 "*** has been disabled for now. You can permanently disable\n" \
66 "*** it by adding the following line to ~/.tigrc\n" \
67 "*** set editor-line-number = no\n"
69 void
70 open_editor(const char *file, unsigned int lineno)
72 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
73 char editor_cmd[SIZEOF_STR];
74 char lineno_cmd[SIZEOF_STR];
75 const char *editor;
76 int argc = 0;
78 editor = getenv("GIT_EDITOR");
79 if (!editor && *opt_editor)
80 editor = opt_editor;
81 if (!editor)
82 editor = getenv("VISUAL");
83 if (!editor)
84 editor = getenv("EDITOR");
85 if (!editor)
86 editor = "vi";
88 string_ncopy(editor_cmd, editor, strlen(editor));
89 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
90 report("Failed to read editor command");
91 return;
94 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
95 editor_argv[argc++] = lineno_cmd;
96 editor_argv[argc] = file;
97 if (!open_external_viewer(editor_argv, repo.cdup, FALSE, TRUE, EDITOR_LINENO_MSG))
98 opt_editor_line_number = FALSE;
102 static void
103 apply_horizontal_split(struct view *base, struct view *view)
105 view->width = base->width;
106 view->height = apply_step(opt_split_view_height, base->height);
107 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
108 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
109 base->height -= view->height;
112 static void
113 apply_vertical_split(struct view *base, struct view *view)
115 view->height = base->height;
116 view->width = apply_step(VSPLIT_SCALE, base->width);
117 view->width = MAX(view->width, MIN_VIEW_WIDTH);
118 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
119 base->width -= view->width;
122 static bool
123 vertical_split_is_enabled(void)
125 if (opt_vertical_split == VERTICAL_SPLIT_AUTO) {
126 int height, width;
128 getmaxyx(stdscr, height, width);
129 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
132 return opt_vertical_split == VERTICAL_SPLIT_VERTICAL;
135 static void
136 redraw_display_separator(bool clear)
138 if (displayed_views() > 1 && vertical_split_is_enabled()) {
139 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
141 if (clear)
142 wclear(display_sep);
143 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
144 wnoutrefresh(display_sep);
148 void
149 resize_display(void)
151 int x, y, i;
152 struct view *base = display[0];
153 struct view *view = display[1] ? display[1] : display[0];
154 bool vsplit;
156 /* Setup window dimensions */
158 getmaxyx(stdscr, base->height, base->width);
160 /* Make room for the status window. */
161 base->height -= 1;
163 vsplit = vertical_split_is_enabled();
165 if (view != base) {
166 if (vsplit) {
167 apply_vertical_split(base, view);
169 /* Make room for the separator bar. */
170 view->width -= 1;
171 } else {
172 apply_horizontal_split(base, view);
175 /* Make room for the title bar. */
176 view->height -= 1;
179 string_format(opt_env_columns, "COLUMNS=%d", base->width);
180 string_format(opt_env_lines, "LINES=%d", base->height);
182 /* Make room for the title bar. */
183 base->height -= 1;
185 x = y = 0;
187 foreach_displayed_view (view, i) {
188 if (!display_win[i]) {
189 display_win[i] = newwin(view->height, view->width, y, x);
190 if (!display_win[i])
191 die("Failed to create %s view", view->name);
193 scrollok(display_win[i], FALSE);
195 display_title[i] = newwin(1, view->width, y + view->height, x);
196 if (!display_title[i])
197 die("Failed to create title window");
199 } else {
200 wresize(display_win[i], view->height, view->width);
201 mvwin(display_win[i], y, x);
202 wresize(display_title[i], 1, view->width);
203 mvwin(display_title[i], y + view->height, x);
206 if (i > 0 && vsplit) {
207 if (!display_sep) {
208 display_sep = newwin(view->height, 1, 0, x - 1);
209 if (!display_sep)
210 die("Failed to create separator window");
212 } else {
213 wresize(display_sep, view->height, 1);
214 mvwin(display_sep, 0, x - 1);
218 view->win = display_win[i];
219 view->title = display_title[i];
221 if (vsplit)
222 x += view->width + 1;
223 else
224 y += view->height + 1;
227 redraw_display_separator(FALSE);
230 void
231 redraw_display(bool clear)
233 struct view *view;
234 int i;
236 foreach_displayed_view (view, i) {
237 if (clear)
238 wclear(view->win);
239 redraw_view(view);
240 update_view_title(view);
243 redraw_display_separator(clear);
246 static bool
247 save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize)
249 int read = mvwinnstr(win, y, 0, buf, bufsize);
251 return read == ERR ? FALSE : fprintf(file, "%s\n", buf) == read + 1;
254 static bool
255 save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize)
257 int read1 = mvwinnstr(left, y, 0, buf, bufsize);
258 int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1);
260 if (read2 == ERR)
261 return FALSE;
262 buf[read1] = '|';
264 return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1;
267 bool
268 save_display(const char *path)
270 int i, width;
271 char *line;
272 FILE *file = fopen(path, "w");
273 bool ok = TRUE;
274 struct view *view = display[0];
276 if (!file)
277 return FALSE;
279 getmaxyx(stdscr, i, width);
280 line = malloc(width + 1);
281 if (!line) {
282 fclose(file);
283 return FALSE;
286 if (view->width < width) {
287 struct view *left = display[0],
288 *right = display[1];
290 for (i = 0; ok && i < left->height; i++)
291 ok = save_window_vline(file, left->win, right->win, i, line, width);
292 if (ok)
293 ok = save_window_vline(file, left->title, right->title, 0, line, width);
294 } else {
295 int j;
297 foreach_displayed_view (view, j) {
298 for (i = 0; ok && i < view->height; i++)
299 ok = save_window_line(file, view->win, i, line, width);
300 if (ok)
301 ok = save_window_line(file, view->title, 0, line, width);
305 free(line);
306 fclose(file);
307 return ok;
311 * Status management
314 /* Whether or not the curses interface has been initialized. */
315 static bool cursed = FALSE;
317 /* Terminal hacks and workarounds. */
318 static bool use_scroll_redrawwin;
319 static bool use_scroll_status_wclear;
321 /* The status window is used for polling keystrokes. */
322 WINDOW *status_win;
324 /* Reading from the prompt? */
325 static bool input_mode = FALSE;
327 static bool status_empty = FALSE;
329 /* Update status and title window. */
330 static bool
331 update_status_window(struct view *view, const char *msg, va_list args)
333 if (input_mode)
334 return FALSE;
336 if (!status_empty || *msg) {
337 wmove(status_win, 0, 0);
338 if (view && view->has_scrolled && use_scroll_status_wclear)
339 wclear(status_win);
340 if (*msg) {
341 vwprintw(status_win, msg, args);
342 status_empty = FALSE;
343 } else {
344 status_empty = TRUE;
346 wclrtoeol(status_win);
347 return TRUE;
350 return FALSE;
353 void
354 update_status(const char *msg, ...)
356 va_list args;
358 va_start(args, msg);
359 update_status_window(display[current_view], msg, args);
360 va_end(args);
363 void
364 report(const char *msg, ...)
366 struct view *view = display[current_view];
367 va_list args;
369 if (!view) {
370 char buf[SIZEOF_STR];
371 int retval;
373 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, TRUE);
374 die("%s", buf);
377 va_start(args, msg);
378 if (update_status_window(view, msg, args))
379 wnoutrefresh(status_win);
380 va_end(args);
382 update_view_title(view);
385 static void
386 done_display(void)
388 endwin();
391 void
392 init_display(void)
394 bool no_display = !!getenv("TIG_NO_DISPLAY");
395 const char *term;
396 int x, y;
398 die_callback = done_display;
399 /* XXX: Restore tty modes and let the OS cleanup the rest! */
400 if (atexit(done_display))
401 die("Failed to register done_display");
403 /* Initialize the curses library */
404 if (!no_display && isatty(STDIN_FILENO)) {
405 cursed = !!initscr();
406 opt_tty = stdin;
407 } else {
408 /* Leave stdin and stdout alone when acting as a pager. */
409 FILE *out_tty;
411 opt_tty = fopen("/dev/tty", "r+");
412 out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty;
413 if (!opt_tty || !out_tty)
414 die("Failed to open /dev/tty");
415 cursed = !!newterm(NULL, out_tty, opt_tty);
418 if (!cursed)
419 die("Failed to initialize curses");
421 nonl(); /* Disable conversion and detect newlines from input. */
422 cbreak(); /* Take input chars one at a time, no wait for \n */
423 noecho(); /* Don't echo input */
424 leaveok(stdscr, FALSE);
426 init_colors();
428 getmaxyx(stdscr, y, x);
429 status_win = newwin(1, x, y - 1, 0);
430 if (!status_win)
431 die("Failed to create status window");
433 /* Enable keyboard mapping */
434 keypad(status_win, TRUE);
435 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
436 enable_mouse(opt_mouse);
438 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
439 set_tabsize(opt_tab_size);
440 #else
441 TABSIZE = opt_tab_size;
442 #endif
444 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
445 if (term && !strcmp(term, "gnome-terminal")) {
446 /* In the gnome-terminal-emulator, the message from
447 * scrolling up one line when impossible followed by
448 * scrolling down one line causes corruption of the
449 * status line. This is fixed by calling wclear. */
450 use_scroll_status_wclear = TRUE;
451 use_scroll_redrawwin = FALSE;
453 } else if (term && !strcmp(term, "xrvt-xpm")) {
454 /* No problems with full optimizations in xrvt-(unicode)
455 * and aterm. */
456 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
458 } else {
459 /* When scrolling in (u)xterm the last line in the
460 * scrolling direction will update slowly. */
461 use_scroll_redrawwin = TRUE;
462 use_scroll_status_wclear = FALSE;
466 static struct io script_io = { -1 };
468 bool
469 open_script(const char *path)
471 return io_open(&script_io, "%s", path);
474 bool
475 is_script_executing(void)
477 return script_io.pipe != -1;
480 static bool
481 read_script(struct key *key, int delay)
483 static struct buffer input_buffer;
484 static const char *line = "";
485 enum status_code code;
487 if (!line || !*line) {
488 if (input_buffer.data && *input_buffer.data == ':') {
489 line = "<Enter>";
490 memset(&input_buffer, 0, sizeof(input_buffer));
492 } else if (!io_get(&script_io, &input_buffer, '\n', TRUE)) {
493 io_done(&script_io);
494 return FALSE;
495 } else {
496 line = input_buffer.data;
500 if (!strcmp(line, ":wait")) {
501 if (delay != 0)
502 line = input_buffer.data = NULL;
503 return FALSE;
506 code = get_key_value(&line, key);
507 if (code != SUCCESS)
508 die("Error reading script: %s", get_status_message(code));
509 return TRUE;
513 get_input(int prompt_position, struct key *key, bool modifiers)
515 struct view *view;
516 int i, key_value, cursor_y, cursor_x;
518 if (prompt_position)
519 input_mode = TRUE;
521 memset(key, 0, sizeof(*key));
523 while (TRUE) {
524 int delay = -1;
526 if (opt_refresh_mode == REFRESH_MODE_PERIODIC) {
527 delay = watch_periodic(opt_refresh_interval);
528 foreach_displayed_view (view, i) {
529 if (view_can_refresh(view) &&
530 watch_dirty(&view->watch))
531 refresh_view(view);
535 foreach_view (view, i) {
536 update_view(view);
537 if (view_is_displayed(view) && view->has_scrolled &&
538 use_scroll_redrawwin)
539 redrawwin(view->win);
540 view->has_scrolled = FALSE;
541 if (view->pipe)
542 delay = 0;
545 /* Update the cursor position. */
546 if (prompt_position) {
547 getbegyx(status_win, cursor_y, cursor_x);
548 cursor_x = prompt_position;
549 } else {
550 view = display[current_view];
551 getbegyx(view->win, cursor_y, cursor_x);
552 cursor_x = view->width - 1;
553 cursor_y += view->pos.lineno - view->pos.offset;
555 setsyx(cursor_y, cursor_x);
557 if (is_script_executing()) {
558 if (!read_script(key, delay))
559 continue;
560 return key->modifiers.multibytes ? OK : key->data.value;
562 } else {
563 /* Refresh, accept single keystroke of input */
564 doupdate();
565 wtimeout(status_win, delay);
566 key_value = wgetch(status_win);
569 /* wgetch() with nodelay() enabled returns ERR when
570 * there's no input. */
571 if (key_value == ERR) {
573 } else if (key_value == KEY_ESC && modifiers) {
574 key->modifiers.escape = 1;
576 } else if (key_value == KEY_RESIZE) {
577 int height, width;
579 getmaxyx(stdscr, height, width);
581 wresize(status_win, 1, width);
582 mvwin(status_win, height - 1, 0);
583 wnoutrefresh(status_win);
584 resize_display();
585 redraw_display(TRUE);
587 } else {
588 int pos, key_length;
590 input_mode = FALSE;
591 if (key_value == erasechar())
592 key_value = KEY_BACKSPACE;
595 * Ctrl-<key> values are represented using a 0x1F
596 * bitmask on the key value. To 'unmap' we assume that:
598 * - Ctrl-Z is handled by Ncurses.
599 * - Ctrl-m is the same as Return/Enter.
600 * - Ctrl-i is the same as Tab.
602 * For all other key values in the range the Ctrl flag
603 * is set and the key value is updated to the proper
604 * ASCII value.
606 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
607 key_value != KEY_RETURN && key_value != KEY_TAB) {
608 key->modifiers.control = 1;
609 key_value = key_value | 0x40;
612 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
613 key->data.value = key_value;
614 return key->data.value;
617 key->modifiers.multibytes = 1;
618 key->data.bytes[0] = key_value;
620 key_length = utf8_char_length(key->data.bytes);
621 nodelay(status_win, TRUE);
622 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
623 key->data.bytes[pos] = wgetch(status_win);
626 return OK;
631 void
632 enable_mouse(bool enable)
634 #ifdef NCURSES_MOUSE_VERSION
635 static bool enabled = FALSE;
637 if (enable != enabled) {
638 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
640 if (mousemask(mask, NULL))
641 mouseinterval(0);
642 enabled = enable;
644 #endif
647 /* vim: set ts=8 sw=8 noexpandtab: */