Fix the help tests
[tig.git] / src / display.c
blob86c5c37e6a0aa455b71825862686c3162f4c1aec
1 /* Copyright (c) 2006-2015 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 static struct io script_io = { -1 };
35 static bool
36 is_script_executing(void)
38 return script_io.pipe != -1;
41 enum status_code
42 open_script(const char *path)
44 if (is_script_executing())
45 return error("Scripts cannot be run from scripts");
47 return io_open(&script_io, "%s", path)
48 ? SUCCESS : error("Failed to open %s", path);
51 bool
52 open_external_viewer(const char *argv[], const char *dir, bool silent, bool confirm, bool refresh, const char *notice)
54 bool ok;
56 if (silent || is_script_executing()) {
57 ok = io_run_bg(argv, dir);
59 } else {
60 endwin(); /* restore original tty modes */
61 ok = io_run_fg(argv, dir);
62 if (confirm || !ok) {
63 if (!ok && *notice)
64 fprintf(stderr, "%s", notice);
65 fprintf(stderr, "Press Enter to continue");
66 getc(opt_tty);
70 if (watch_update(WATCH_EVENT_AFTER_COMMAND) && refresh) {
71 struct view *view;
72 int i;
74 foreach_displayed_view (view, i) {
75 if (watch_dirty(&view->watch))
76 refresh_view(view);
79 redraw_display(true);
80 return ok;
83 #define EDITOR_LINENO_MSG \
84 "*** Your editor reported an error while opening the file.\n" \
85 "*** This is probably because it doesn't support the line\n" \
86 "*** number argument added automatically. The line number\n" \
87 "*** has been disabled for now. You can permanently disable\n" \
88 "*** it by adding the following line to ~/.tigrc\n" \
89 "*** set editor-line-number = no\n"
91 void
92 open_editor(const char *file, unsigned int lineno)
94 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
95 char editor_cmd[SIZEOF_STR];
96 char lineno_cmd[SIZEOF_STR];
97 const char *editor;
98 int argc = 0;
100 editor = getenv("GIT_EDITOR");
101 if (!editor && *opt_editor)
102 editor = opt_editor;
103 if (!editor)
104 editor = getenv("VISUAL");
105 if (!editor)
106 editor = getenv("EDITOR");
107 if (!editor)
108 editor = "vi";
110 string_ncopy(editor_cmd, editor, strlen(editor));
111 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
112 report("Failed to read editor command");
113 return;
116 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
117 editor_argv[argc++] = lineno_cmd;
118 editor_argv[argc] = file;
119 if (!open_external_viewer(editor_argv, repo.cdup, false, false, true, EDITOR_LINENO_MSG))
120 opt_editor_line_number = false;
124 static void
125 apply_horizontal_split(struct view *base, struct view *view)
127 view->width = base->width;
128 view->height = apply_step(opt_split_view_height, base->height);
129 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
130 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
131 base->height -= view->height;
135 apply_vertical_split(int base_width)
137 int width = apply_step(opt_split_view_width, base_width);
139 width = MAX(width, MIN_VIEW_WIDTH);
140 width = MIN(width, base_width - MIN_VIEW_WIDTH);
142 return width;
145 bool
146 vertical_split_is_enabled(enum vertical_split vsplit, int height, int width)
148 if (vsplit == VERTICAL_SPLIT_AUTO)
149 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
150 return vsplit == VERTICAL_SPLIT_VERTICAL;
153 static void
154 redraw_display_separator(bool clear)
156 if (display_sep) {
157 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
159 if (clear)
160 wclear(display_sep);
161 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
162 wnoutrefresh(display_sep);
166 static void create_or_move_display_separator(int height, int x)
168 if (!display_sep) {
169 display_sep = newwin(height, 1, 0, x);
170 if (!display_sep)
171 die("Failed to create separator window");
173 } else {
174 wresize(display_sep, height, 1);
175 mvwin(display_sep, 0, x);
179 static void remove_display_separator(void)
181 if (display_sep) {
182 delwin(display_sep);
183 display_sep = NULL;
187 void
188 resize_display(void)
190 int x, y, i;
191 int height, width;
192 struct view *base = display[0];
193 struct view *view = display[1] ? display[1] : display[0];
194 bool vsplit;
196 /* Setup window dimensions */
198 getmaxyx(stdscr, height, width);
200 /* Make room for the status window. */
201 base->height = height - 1;
202 base->width = width;
204 vsplit = vertical_split_is_enabled(opt_vertical_split, height, width);
206 if (view != base) {
207 if (vsplit) {
208 view->height = base->height;
209 view->width = apply_vertical_split(base->width);
210 base->width -= view->width;
212 /* Make room for the separator bar. */
213 view->width -= 1;
215 create_or_move_display_separator(base->height, base->width);
216 redraw_display_separator(false);
217 } else {
218 remove_display_separator();
219 apply_horizontal_split(base, view);
222 /* Make room for the title bar. */
223 view->height -= 1;
225 } else {
226 remove_display_separator();
229 /* Make room for the title bar. */
230 base->height -= 1;
232 x = y = 0;
234 foreach_displayed_view (view, i) {
235 if (!display_win[i]) {
236 display_win[i] = newwin(view->height, view->width, y, x);
237 if (!display_win[i])
238 die("Failed to create %s view", view->name);
240 scrollok(display_win[i], false);
242 display_title[i] = newwin(1, view->width, y + view->height, x);
243 if (!display_title[i])
244 die("Failed to create title window");
246 } else {
247 wresize(display_win[i], view->height, view->width);
248 mvwin(display_win[i], y, x);
249 wresize(display_title[i], 1, view->width);
250 mvwin(display_title[i], y + view->height, x);
253 view->win = display_win[i];
254 view->title = display_title[i];
256 if (vsplit)
257 x += view->width + 1;
258 else
259 y += view->height + 1;
262 redraw_display_separator(false);
265 void
266 redraw_display(bool clear)
268 struct view *view;
269 int i;
271 foreach_displayed_view (view, i) {
272 if (clear)
273 wclear(view->win);
274 redraw_view(view);
275 update_view_title(view);
278 redraw_display_separator(clear);
281 static bool
282 save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize)
284 int read = mvwinnstr(win, y, 0, buf, bufsize);
286 return read == ERR ? false : fprintf(file, "%s\n", buf) == read + 1;
289 static bool
290 save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize)
292 int read1 = mvwinnstr(left, y, 0, buf, bufsize);
293 int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1);
295 if (read2 == ERR)
296 return false;
297 buf[read1] = '|';
299 return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1;
302 bool
303 save_display(const char *path)
305 int i, width;
306 size_t linelen;
307 char *line;
308 FILE *file = fopen(path, "w");
309 bool ok = true;
310 struct view *view = display[0];
312 if (!file)
313 return false;
315 getmaxyx(stdscr, i, width);
316 linelen = width * 4;
317 line = malloc(linelen + 1);
318 if (!line) {
319 fclose(file);
320 return false;
323 if (view->width < width && display[1]) {
324 struct view *left = display[0],
325 *right = display[1];
327 for (i = 0; ok && i < left->height; i++)
328 ok = save_window_vline(file, left->win, right->win, i, line, linelen);
329 if (ok)
330 ok = save_window_vline(file, left->title, right->title, 0, line, linelen);
331 } else {
332 int j;
334 foreach_displayed_view (view, j) {
335 for (i = 0; ok && i < view->height; i++)
336 ok = save_window_line(file, view->win, i, line, linelen);
337 if (ok)
338 ok = save_window_line(file, view->title, 0, line, linelen);
342 free(line);
343 fclose(file);
344 return ok;
348 * Status management
351 /* Whether or not the curses interface has been initialized. */
352 static bool cursed = false;
354 /* Terminal hacks and workarounds. */
355 static bool use_scroll_redrawwin;
356 static bool use_scroll_status_wclear;
358 /* The status window is used for polling keystrokes. */
359 WINDOW *status_win;
361 /* Reading from the prompt? */
362 static bool input_mode = false;
364 static bool status_empty = false;
366 /* Update status and title window. */
367 static bool
368 update_status_window(struct view *view, const char *msg, va_list args)
370 if (input_mode)
371 return false;
373 if (!status_empty || *msg) {
374 wmove(status_win, 0, 0);
375 if (view && view->has_scrolled && use_scroll_status_wclear)
376 wclear(status_win);
377 if (*msg) {
378 vwprintw(status_win, msg, args);
379 status_empty = false;
380 } else {
381 status_empty = true;
383 wclrtoeol(status_win);
384 return true;
387 return false;
390 void
391 update_status(const char *msg, ...)
393 va_list args;
395 va_start(args, msg);
396 update_status_window(display[current_view], msg, args);
397 va_end(args);
400 void
401 report(const char *msg, ...)
403 struct view *view = display[current_view];
404 va_list args;
406 if (!view) {
407 char buf[SIZEOF_STR];
408 int retval;
410 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, true);
411 die("%s", buf);
414 va_start(args, msg);
415 if (update_status_window(view, msg, args))
416 wnoutrefresh(status_win);
417 va_end(args);
419 update_view_title(view);
422 static void
423 done_display(void)
425 if (cursed)
426 endwin();
427 cursed = false;
430 void
431 init_display(void)
433 bool no_display = !!getenv("TIG_NO_DISPLAY");
434 const char *term;
435 int x, y;
437 die_callback = done_display;
438 /* XXX: Restore tty modes and let the OS cleanup the rest! */
439 if (atexit(done_display))
440 die("Failed to register done_display");
442 /* Initialize the curses library */
443 if (!no_display && isatty(STDIN_FILENO)) {
444 cursed = !!initscr();
445 opt_tty = stdin;
446 } else {
447 /* Leave stdin and stdout alone when acting as a pager. */
448 FILE *out_tty;
450 opt_tty = fopen("/dev/tty", "r+");
451 out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty;
452 if (!opt_tty || !out_tty)
453 die("Failed to open /dev/tty");
454 cursed = !!newterm(NULL, out_tty, opt_tty);
457 if (!cursed)
458 die("Failed to initialize curses");
460 nonl(); /* Disable conversion and detect newlines from input. */
461 cbreak(); /* Take input chars one at a time, no wait for \n */
462 noecho(); /* Don't echo input */
463 leaveok(stdscr, false);
465 init_colors();
467 getmaxyx(stdscr, y, x);
468 status_win = newwin(1, x, y - 1, 0);
469 if (!status_win)
470 die("Failed to create status window");
472 /* Enable keyboard mapping */
473 keypad(status_win, true);
474 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
475 enable_mouse(opt_mouse);
477 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
478 set_tabsize(opt_tab_size);
479 #else
480 TABSIZE = opt_tab_size;
481 #endif
483 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
484 if (term && !strcmp(term, "gnome-terminal")) {
485 /* In the gnome-terminal-emulator, the warning message
486 * shown when scrolling up one line while the cursor is
487 * on the first line followed by scrolling down one line
488 * corrupts the status line. This is fixed by calling
489 * wclear. */
490 use_scroll_status_wclear = true;
491 use_scroll_redrawwin = false;
493 } else if (term && !strcmp(term, "xrvt-xpm")) {
494 /* No problems with full optimizations in xrvt-(unicode)
495 * and aterm. */
496 use_scroll_status_wclear = use_scroll_redrawwin = false;
498 } else {
499 /* When scrolling in (u)xterm the last line in the
500 * scrolling direction will update slowly. */
501 use_scroll_redrawwin = true;
502 use_scroll_status_wclear = false;
506 static bool
507 read_script(struct key *key)
509 static struct buffer input_buffer;
510 static const char *line = "";
511 enum status_code code;
513 if (!line || !*line) {
514 if (input_buffer.data && *input_buffer.data == ':') {
515 line = "<Enter>";
516 memset(&input_buffer, 0, sizeof(input_buffer));
518 } else if (!io_get(&script_io, &input_buffer, '\n', true)) {
519 io_done(&script_io);
520 return false;
521 } else {
522 line = input_buffer.data;
526 code = get_key_value(&line, key);
527 if (code != SUCCESS)
528 die("Error reading script: %s", get_status_message(code));
529 return true;
533 get_input_char(void)
535 if (is_script_executing()) {
536 static struct key key;
537 static int bytes_pos;
539 if (!key.modifiers.multibytes || bytes_pos >= strlen(key.data.bytes)) {
540 if (!read_script(&key))
541 return 0;
542 bytes_pos = 0;
545 if (!key.modifiers.multibytes) {
546 if (key.data.value < 128)
547 return key.data.value;
548 die("Only ASCII control characters can be used in prompts: %d", key.data.value);
551 return key.data.bytes[bytes_pos++];
554 return getc(opt_tty);
558 get_input(int prompt_position, struct key *key)
560 struct view *view;
561 int i, key_value, cursor_y, cursor_x;
563 if (prompt_position > 0)
564 input_mode = true;
566 memset(key, 0, sizeof(*key));
568 while (true) {
569 int delay = -1;
571 if (opt_refresh_mode == REFRESH_MODE_PERIODIC) {
572 delay = watch_periodic(opt_refresh_interval);
573 foreach_displayed_view (view, i) {
574 if (view_can_refresh(view) &&
575 watch_dirty(&view->watch))
576 refresh_view(view);
580 foreach_view (view, i) {
581 update_view(view);
582 if (view_is_displayed(view) && view->has_scrolled &&
583 use_scroll_redrawwin)
584 redrawwin(view->win);
585 view->has_scrolled = false;
586 if (view->pipe)
587 delay = 0;
590 /* Update the cursor position. */
591 if (prompt_position) {
592 getbegyx(status_win, cursor_y, cursor_x);
593 cursor_x = prompt_position;
594 } else {
595 view = display[current_view];
596 getbegyx(view->win, cursor_y, cursor_x);
597 cursor_x += view->width - 1;
598 cursor_y += view->pos.lineno - view->pos.offset;
600 set_cursor_pos(cursor_y, cursor_x);
602 if (is_script_executing()) {
603 /* Wait for the current command to complete. */
604 if (delay == 0 || !read_script(key))
605 continue;
606 return key->modifiers.multibytes ? OK : key->data.value;
608 } else {
609 /* Refresh, accept single keystroke of input */
610 doupdate();
611 wtimeout(status_win, delay);
612 key_value = wgetch(status_win);
615 /* wgetch() with nodelay() enabled returns ERR when
616 * there's no input. */
617 if (key_value == ERR) {
619 } else if (key_value == KEY_RESIZE) {
620 int height, width;
622 getmaxyx(stdscr, height, width);
624 wresize(status_win, 1, width);
625 mvwin(status_win, height - 1, 0);
626 wnoutrefresh(status_win);
627 resize_display();
628 redraw_display(true);
630 } else {
631 int pos, key_length;
633 input_mode = false;
634 if (key_value == erasechar())
635 key_value = KEY_BACKSPACE;
638 * Ctrl-<key> values are represented using a 0x1F
639 * bitmask on the key value. To 'unmap' we assume that:
641 * - Ctrl-Z is handled by Ncurses.
642 * - Ctrl-m is the same as Return/Enter.
643 * - Ctrl-i is the same as Tab.
645 * For all other key values in the range the Ctrl flag
646 * is set and the key value is updated to the proper
647 * ASCII value.
649 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
650 key_value != KEY_RETURN && key_value != KEY_TAB) {
651 key->modifiers.control = 1;
652 key_value = key_value | 0x40;
655 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
656 key->data.value = key_value;
657 return key->data.value;
660 key->modifiers.multibytes = 1;
661 key->data.bytes[0] = key_value;
663 key_length = utf8_char_length(key->data.bytes);
664 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
665 key->data.bytes[pos] = wgetch(status_win);
668 return OK;
673 void
674 enable_mouse(bool enable)
676 #ifdef NCURSES_MOUSE_VERSION
677 static bool enabled = false;
679 if (enable != enabled) {
680 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
682 if (mousemask(mask, NULL))
683 mouseinterval(0);
684 enabled = enable;
686 #endif
689 /* vim: set ts=8 sw=8 noexpandtab: */