Change test scripts to always wait for previous command to finish
[tig.git] / src / display.c
blobf813f08e9c6be9f81798bc1d7d56c2dc4f71203b
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 silent, bool confirm, bool refresh, const char *notice)
36 bool ok;
38 if (silent) {
39 ok = io_run_bg(argv);
41 } else {
42 def_prog_mode(); /* save current tty modes */
43 endwin(); /* restore original tty modes */
44 ok = io_run_fg(argv, dir);
45 if (confirm || !ok) {
46 if (!ok && *notice)
47 fprintf(stderr, "%s", notice);
48 if (!is_script_executing()) {
49 fprintf(stderr, "Press Enter to continue");
50 getc(opt_tty);
53 reset_prog_mode();
56 if (watch_update(WATCH_EVENT_AFTER_COMMAND) && refresh) {
57 struct view *view;
58 int i;
60 foreach_displayed_view (view, i) {
61 if (watch_dirty(&view->watch))
62 refresh_view(view);
65 redraw_display(TRUE);
66 return ok;
69 #define EDITOR_LINENO_MSG \
70 "*** Your editor reported an error while opening the file.\n" \
71 "*** This is probably because it doesn't support the line\n" \
72 "*** number argument added automatically. The line number\n" \
73 "*** has been disabled for now. You can permanently disable\n" \
74 "*** it by adding the following line to ~/.tigrc\n" \
75 "*** set editor-line-number = no\n"
77 void
78 open_editor(const char *file, unsigned int lineno)
80 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
81 char editor_cmd[SIZEOF_STR];
82 char lineno_cmd[SIZEOF_STR];
83 const char *editor;
84 int argc = 0;
86 editor = getenv("GIT_EDITOR");
87 if (!editor && *opt_editor)
88 editor = opt_editor;
89 if (!editor)
90 editor = getenv("VISUAL");
91 if (!editor)
92 editor = getenv("EDITOR");
93 if (!editor)
94 editor = "vi";
96 string_ncopy(editor_cmd, editor, strlen(editor));
97 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
98 report("Failed to read editor command");
99 return;
102 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
103 editor_argv[argc++] = lineno_cmd;
104 editor_argv[argc] = file;
105 if (!open_external_viewer(editor_argv, repo.cdup, FALSE, FALSE, TRUE, EDITOR_LINENO_MSG))
106 opt_editor_line_number = FALSE;
110 static void
111 apply_horizontal_split(struct view *base, struct view *view)
113 view->width = base->width;
114 view->height = apply_step(opt_split_view_height, base->height);
115 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
116 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
117 base->height -= view->height;
120 static void
121 apply_vertical_split(struct view *base, struct view *view)
123 view->height = base->height;
124 view->width = apply_step(VSPLIT_SCALE, base->width);
125 view->width = MAX(view->width, MIN_VIEW_WIDTH);
126 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
127 base->width -= view->width;
130 static bool
131 vertical_split_is_enabled(void)
133 if (opt_vertical_split == VERTICAL_SPLIT_AUTO) {
134 int height, width;
136 getmaxyx(stdscr, height, width);
137 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
140 return opt_vertical_split == VERTICAL_SPLIT_VERTICAL;
143 static void
144 redraw_display_separator(bool clear)
146 if (displayed_views() > 1 && vertical_split_is_enabled()) {
147 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
149 if (clear)
150 wclear(display_sep);
151 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
152 wnoutrefresh(display_sep);
156 void
157 resize_display(void)
159 int x, y, i;
160 struct view *base = display[0];
161 struct view *view = display[1] ? display[1] : display[0];
162 bool vsplit;
164 /* Setup window dimensions */
166 getmaxyx(stdscr, base->height, base->width);
168 /* Make room for the status window. */
169 base->height -= 1;
171 vsplit = vertical_split_is_enabled();
173 if (view != base) {
174 if (vsplit) {
175 apply_vertical_split(base, view);
177 /* Make room for the separator bar. */
178 view->width -= 1;
179 } else {
180 apply_horizontal_split(base, view);
183 /* Make room for the title bar. */
184 view->height -= 1;
187 string_format(opt_env_columns, "COLUMNS=%d", base->width);
188 string_format(opt_env_lines, "LINES=%d", base->height);
190 /* Make room for the title bar. */
191 base->height -= 1;
193 x = y = 0;
195 foreach_displayed_view (view, i) {
196 if (!display_win[i]) {
197 display_win[i] = newwin(view->height, view->width, y, x);
198 if (!display_win[i])
199 die("Failed to create %s view", view->name);
201 scrollok(display_win[i], FALSE);
203 display_title[i] = newwin(1, view->width, y + view->height, x);
204 if (!display_title[i])
205 die("Failed to create title window");
207 } else {
208 wresize(display_win[i], view->height, view->width);
209 mvwin(display_win[i], y, x);
210 wresize(display_title[i], 1, view->width);
211 mvwin(display_title[i], y + view->height, x);
214 if (i > 0 && vsplit) {
215 if (!display_sep) {
216 display_sep = newwin(view->height, 1, 0, x - 1);
217 if (!display_sep)
218 die("Failed to create separator window");
220 } else {
221 wresize(display_sep, view->height, 1);
222 mvwin(display_sep, 0, x - 1);
226 view->win = display_win[i];
227 view->title = display_title[i];
229 if (vsplit)
230 x += view->width + 1;
231 else
232 y += view->height + 1;
235 redraw_display_separator(FALSE);
238 void
239 redraw_display(bool clear)
241 struct view *view;
242 int i;
244 foreach_displayed_view (view, i) {
245 if (clear)
246 wclear(view->win);
247 redraw_view(view);
248 update_view_title(view);
251 redraw_display_separator(clear);
254 static bool
255 save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize)
257 int read = mvwinnstr(win, y, 0, buf, bufsize);
259 return read == ERR ? FALSE : fprintf(file, "%s\n", buf) == read + 1;
262 static bool
263 save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize)
265 int read1 = mvwinnstr(left, y, 0, buf, bufsize);
266 int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1);
268 if (read2 == ERR)
269 return FALSE;
270 buf[read1] = '|';
272 return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1;
275 bool
276 save_display(const char *path)
278 int i, width;
279 char *line;
280 FILE *file = fopen(path, "w");
281 bool ok = TRUE;
282 struct view *view = display[0];
284 if (!file)
285 return FALSE;
287 getmaxyx(stdscr, i, width);
288 line = malloc(width + 1);
289 if (!line) {
290 fclose(file);
291 return FALSE;
294 if (view->width < width) {
295 struct view *left = display[0],
296 *right = display[1];
298 for (i = 0; ok && i < left->height; i++)
299 ok = save_window_vline(file, left->win, right->win, i, line, width);
300 if (ok)
301 ok = save_window_vline(file, left->title, right->title, 0, line, width);
302 } else {
303 int j;
305 foreach_displayed_view (view, j) {
306 for (i = 0; ok && i < view->height; i++)
307 ok = save_window_line(file, view->win, i, line, width);
308 if (ok)
309 ok = save_window_line(file, view->title, 0, line, width);
313 free(line);
314 fclose(file);
315 return ok;
319 * Status management
322 /* Whether or not the curses interface has been initialized. */
323 static bool cursed = FALSE;
325 /* Terminal hacks and workarounds. */
326 static bool use_scroll_redrawwin;
327 static bool use_scroll_status_wclear;
329 /* The status window is used for polling keystrokes. */
330 WINDOW *status_win;
332 /* Reading from the prompt? */
333 static bool input_mode = FALSE;
335 static bool status_empty = FALSE;
337 /* Update status and title window. */
338 static bool
339 update_status_window(struct view *view, const char *msg, va_list args)
341 if (input_mode)
342 return FALSE;
344 if (!status_empty || *msg) {
345 wmove(status_win, 0, 0);
346 if (view && view->has_scrolled && use_scroll_status_wclear)
347 wclear(status_win);
348 if (*msg) {
349 vwprintw(status_win, msg, args);
350 status_empty = FALSE;
351 } else {
352 status_empty = TRUE;
354 wclrtoeol(status_win);
355 return TRUE;
358 return FALSE;
361 void
362 update_status(const char *msg, ...)
364 va_list args;
366 va_start(args, msg);
367 update_status_window(display[current_view], msg, args);
368 va_end(args);
371 void
372 report(const char *msg, ...)
374 struct view *view = display[current_view];
375 va_list args;
377 if (!view) {
378 char buf[SIZEOF_STR];
379 int retval;
381 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, TRUE);
382 die("%s", buf);
385 va_start(args, msg);
386 if (update_status_window(view, msg, args))
387 wnoutrefresh(status_win);
388 va_end(args);
390 update_view_title(view);
393 static void
394 done_display(void)
396 endwin();
399 void
400 init_display(void)
402 bool no_display = !!getenv("TIG_NO_DISPLAY");
403 const char *term;
404 int x, y;
406 die_callback = done_display;
407 /* XXX: Restore tty modes and let the OS cleanup the rest! */
408 if (atexit(done_display))
409 die("Failed to register done_display");
411 /* Initialize the curses library */
412 if (!no_display && isatty(STDIN_FILENO)) {
413 cursed = !!initscr();
414 opt_tty = stdin;
415 } else {
416 /* Leave stdin and stdout alone when acting as a pager. */
417 FILE *out_tty;
419 opt_tty = fopen("/dev/tty", "r+");
420 out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty;
421 if (!opt_tty || !out_tty)
422 die("Failed to open /dev/tty");
423 cursed = !!newterm(NULL, out_tty, opt_tty);
426 if (!cursed)
427 die("Failed to initialize curses");
429 nonl(); /* Disable conversion and detect newlines from input. */
430 cbreak(); /* Take input chars one at a time, no wait for \n */
431 noecho(); /* Don't echo input */
432 leaveok(stdscr, FALSE);
434 init_colors();
436 getmaxyx(stdscr, y, x);
437 status_win = newwin(1, x, y - 1, 0);
438 if (!status_win)
439 die("Failed to create status window");
441 /* Enable keyboard mapping */
442 keypad(status_win, TRUE);
443 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
444 enable_mouse(opt_mouse);
446 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
447 set_tabsize(opt_tab_size);
448 #else
449 TABSIZE = opt_tab_size;
450 #endif
452 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
453 if (term && !strcmp(term, "gnome-terminal")) {
454 /* In the gnome-terminal-emulator, the message from
455 * scrolling up one line when impossible followed by
456 * scrolling down one line causes corruption of the
457 * status line. This is fixed by calling wclear. */
458 use_scroll_status_wclear = TRUE;
459 use_scroll_redrawwin = FALSE;
461 } else if (term && !strcmp(term, "xrvt-xpm")) {
462 /* No problems with full optimizations in xrvt-(unicode)
463 * and aterm. */
464 use_scroll_status_wclear = use_scroll_redrawwin = FALSE;
466 } else {
467 /* When scrolling in (u)xterm the last line in the
468 * scrolling direction will update slowly. */
469 use_scroll_redrawwin = TRUE;
470 use_scroll_status_wclear = FALSE;
474 static struct io script_io = { -1 };
476 bool
477 open_script(const char *path)
479 return io_open(&script_io, "%s", path);
482 bool
483 is_script_executing(void)
485 return script_io.pipe != -1;
488 static bool
489 read_script(struct key *key)
491 static struct buffer input_buffer;
492 static const char *line = "";
493 enum status_code code;
495 if (!line || !*line) {
496 if (input_buffer.data && *input_buffer.data == ':') {
497 line = "<Enter>";
498 memset(&input_buffer, 0, sizeof(input_buffer));
500 } else if (!io_get(&script_io, &input_buffer, '\n', TRUE)) {
501 io_done(&script_io);
502 return FALSE;
503 } else {
504 line = input_buffer.data;
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))
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 /* Wait for the current command to complete. */
586 if (delay == 0 || !read_script(key))
587 continue;
588 return key->modifiers.multibytes ? OK : key->data.value;
590 } else {
591 /* Refresh, accept single keystroke of input */
592 doupdate();
593 wtimeout(status_win, delay);
594 key_value = wgetch(status_win);
597 /* wgetch() with nodelay() enabled returns ERR when
598 * there's no input. */
599 if (key_value == ERR) {
601 } else if (key_value == KEY_ESC && modifiers) {
602 key->modifiers.escape = 1;
604 } else if (key_value == KEY_RESIZE) {
605 int height, width;
607 getmaxyx(stdscr, height, width);
609 wresize(status_win, 1, width);
610 mvwin(status_win, height - 1, 0);
611 wnoutrefresh(status_win);
612 resize_display();
613 redraw_display(TRUE);
615 } else {
616 int pos, key_length;
618 input_mode = FALSE;
619 if (key_value == erasechar())
620 key_value = KEY_BACKSPACE;
623 * Ctrl-<key> values are represented using a 0x1F
624 * bitmask on the key value. To 'unmap' we assume that:
626 * - Ctrl-Z is handled by Ncurses.
627 * - Ctrl-m is the same as Return/Enter.
628 * - Ctrl-i is the same as Tab.
630 * For all other key values in the range the Ctrl flag
631 * is set and the key value is updated to the proper
632 * ASCII value.
634 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
635 key_value != KEY_RETURN && key_value != KEY_TAB) {
636 key->modifiers.control = 1;
637 key_value = key_value | 0x40;
640 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
641 key->data.value = key_value;
642 return key->data.value;
645 key->modifiers.multibytes = 1;
646 key->data.bytes[0] = key_value;
648 key_length = utf8_char_length(key->data.bytes);
649 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
650 key->data.bytes[pos] = wgetch(status_win);
653 return OK;
658 void
659 enable_mouse(bool enable)
661 #ifdef NCURSES_MOUSE_VERSION
662 static bool enabled = FALSE;
664 if (enable != enabled) {
665 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
667 if (mousemask(mask, NULL))
668 mouseinterval(0);
669 enabled = enable;
671 #endif
674 /* vim: set ts=8 sw=8 noexpandtab: */