Revisit ban on FALSE and TRUE to also replace getsyx
[tig.git] / src / display.c
blob8ff859bddf1d20f71e1f3d48730a0e5879f28bff
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) {
57 ok = io_run_bg(argv, dir);
59 } else {
60 def_prog_mode(); /* save current tty modes */
61 endwin(); /* restore original tty modes */
62 ok = io_run_fg(argv, dir);
63 if (confirm || !ok) {
64 if (!ok && *notice)
65 fprintf(stderr, "%s", notice);
66 if (!is_script_executing()) {
67 fprintf(stderr, "Press Enter to continue");
68 getc(opt_tty);
71 reset_prog_mode();
74 if (watch_update(WATCH_EVENT_AFTER_COMMAND) && refresh) {
75 struct view *view;
76 int i;
78 foreach_displayed_view (view, i) {
79 if (watch_dirty(&view->watch))
80 refresh_view(view);
83 redraw_display(true);
84 return ok;
87 #define EDITOR_LINENO_MSG \
88 "*** Your editor reported an error while opening the file.\n" \
89 "*** This is probably because it doesn't support the line\n" \
90 "*** number argument added automatically. The line number\n" \
91 "*** has been disabled for now. You can permanently disable\n" \
92 "*** it by adding the following line to ~/.tigrc\n" \
93 "*** set editor-line-number = no\n"
95 void
96 open_editor(const char *file, unsigned int lineno)
98 const char *editor_argv[SIZEOF_ARG + 3] = { "vi", file, NULL };
99 char editor_cmd[SIZEOF_STR];
100 char lineno_cmd[SIZEOF_STR];
101 const char *editor;
102 int argc = 0;
104 editor = getenv("GIT_EDITOR");
105 if (!editor && *opt_editor)
106 editor = opt_editor;
107 if (!editor)
108 editor = getenv("VISUAL");
109 if (!editor)
110 editor = getenv("EDITOR");
111 if (!editor)
112 editor = "vi";
114 string_ncopy(editor_cmd, editor, strlen(editor));
115 if (!argv_from_string_no_quotes(editor_argv, &argc, editor_cmd)) {
116 report("Failed to read editor command");
117 return;
120 if (lineno && opt_editor_line_number && string_format(lineno_cmd, "+%u", lineno))
121 editor_argv[argc++] = lineno_cmd;
122 editor_argv[argc] = file;
123 if (!open_external_viewer(editor_argv, repo.cdup, false, false, true, EDITOR_LINENO_MSG))
124 opt_editor_line_number = false;
128 static void
129 apply_horizontal_split(struct view *base, struct view *view)
131 view->width = base->width;
132 view->height = apply_step(opt_split_view_height, base->height);
133 view->height = MAX(view->height, MIN_VIEW_HEIGHT);
134 view->height = MIN(view->height, base->height - MIN_VIEW_HEIGHT);
135 base->height -= view->height;
138 static void
139 apply_vertical_split(struct view *base, struct view *view)
141 view->height = base->height;
142 view->width = apply_step(opt_split_view_width, base->width);
143 view->width = MAX(view->width, MIN_VIEW_WIDTH);
144 view->width = MIN(view->width, base->width - MIN_VIEW_WIDTH);
145 base->width -= view->width;
148 static bool
149 vertical_split_is_enabled(void)
151 if (opt_vertical_split == VERTICAL_SPLIT_AUTO) {
152 int height, width;
154 getmaxyx(stdscr, height, width);
155 return width > 160 || width * VSPLIT_SCALE > (height - 1) * 2;
158 return opt_vertical_split == VERTICAL_SPLIT_VERTICAL;
161 static void
162 redraw_display_separator(bool clear)
164 if (displayed_views() > 1 && vertical_split_is_enabled()) {
165 chtype separator = opt_line_graphics ? ACS_VLINE : '|';
167 if (clear)
168 wclear(display_sep);
169 wbkgd(display_sep, separator + get_line_attr(NULL, LINE_TITLE_BLUR));
170 wnoutrefresh(display_sep);
174 void
175 resize_display(void)
177 int x, y, i;
178 struct view *base = display[0];
179 struct view *view = display[1] ? display[1] : display[0];
180 bool vsplit;
182 /* Setup window dimensions */
184 getmaxyx(stdscr, base->height, base->width);
186 /* Make room for the status window. */
187 base->height -= 1;
189 vsplit = vertical_split_is_enabled();
191 if (view != base) {
192 if (vsplit) {
193 apply_vertical_split(base, view);
195 /* Make room for the separator bar. */
196 view->width -= 1;
197 } else {
198 apply_horizontal_split(base, view);
201 /* Make room for the title bar. */
202 view->height -= 1;
205 string_format(opt_env_columns, "COLUMNS=%d", base->width);
206 string_format(opt_env_lines, "LINES=%d", base->height);
208 /* Make room for the title bar. */
209 base->height -= 1;
211 x = y = 0;
213 foreach_displayed_view (view, i) {
214 if (!display_win[i]) {
215 display_win[i] = newwin(view->height, view->width, y, x);
216 if (!display_win[i])
217 die("Failed to create %s view", view->name);
219 scrollok(display_win[i], false);
221 display_title[i] = newwin(1, view->width, y + view->height, x);
222 if (!display_title[i])
223 die("Failed to create title window");
225 } else {
226 wresize(display_win[i], view->height, view->width);
227 mvwin(display_win[i], y, x);
228 wresize(display_title[i], 1, view->width);
229 mvwin(display_title[i], y + view->height, x);
232 if (i > 0 && vsplit) {
233 if (!display_sep) {
234 display_sep = newwin(view->height, 1, 0, x - 1);
235 if (!display_sep)
236 die("Failed to create separator window");
238 } else {
239 wresize(display_sep, view->height, 1);
240 mvwin(display_sep, 0, x - 1);
244 view->win = display_win[i];
245 view->title = display_title[i];
247 if (vsplit)
248 x += view->width + 1;
249 else
250 y += view->height + 1;
253 redraw_display_separator(false);
256 void
257 redraw_display(bool clear)
259 struct view *view;
260 int i;
262 foreach_displayed_view (view, i) {
263 if (clear)
264 wclear(view->win);
265 redraw_view(view);
266 update_view_title(view);
269 redraw_display_separator(clear);
272 static bool
273 save_window_line(FILE *file, WINDOW *win, int y, char *buf, size_t bufsize)
275 int read = mvwinnstr(win, y, 0, buf, bufsize);
277 return read == ERR ? false : fprintf(file, "%s\n", buf) == read + 1;
280 static bool
281 save_window_vline(FILE *file, WINDOW *left, WINDOW *right, int y, char *buf, size_t bufsize)
283 int read1 = mvwinnstr(left, y, 0, buf, bufsize);
284 int read2 = read1 == ERR ? ERR : mvwinnstr(right, y, 0, buf + read1 + 1, bufsize - read1 - 1);
286 if (read2 == ERR)
287 return false;
288 buf[read1] = '|';
290 return fprintf(file, "%s\n", buf) == read1 + 1 + read2 + 1;
293 bool
294 save_display(const char *path)
296 int i, width;
297 size_t linelen;
298 char *line;
299 FILE *file = fopen(path, "w");
300 bool ok = true;
301 struct view *view = display[0];
303 if (!file)
304 return false;
306 getmaxyx(stdscr, i, width);
307 linelen = width * 4;
308 line = malloc(linelen + 1);
309 if (!line) {
310 fclose(file);
311 return false;
314 if (view->width < width && display[1]) {
315 struct view *left = display[0],
316 *right = display[1];
318 for (i = 0; ok && i < left->height; i++)
319 ok = save_window_vline(file, left->win, right->win, i, line, linelen);
320 if (ok)
321 ok = save_window_vline(file, left->title, right->title, 0, line, linelen);
322 } else {
323 int j;
325 foreach_displayed_view (view, j) {
326 for (i = 0; ok && i < view->height; i++)
327 ok = save_window_line(file, view->win, i, line, linelen);
328 if (ok)
329 ok = save_window_line(file, view->title, 0, line, linelen);
333 free(line);
334 fclose(file);
335 return ok;
339 * Status management
342 /* Whether or not the curses interface has been initialized. */
343 static bool cursed = false;
345 /* Terminal hacks and workarounds. */
346 static bool use_scroll_redrawwin;
347 static bool use_scroll_status_wclear;
349 /* The status window is used for polling keystrokes. */
350 WINDOW *status_win;
352 /* Reading from the prompt? */
353 static bool input_mode = false;
355 static bool status_empty = false;
357 /* Update status and title window. */
358 static bool
359 update_status_window(struct view *view, const char *msg, va_list args)
361 if (input_mode)
362 return false;
364 if (!status_empty || *msg) {
365 wmove(status_win, 0, 0);
366 if (view && view->has_scrolled && use_scroll_status_wclear)
367 wclear(status_win);
368 if (*msg) {
369 vwprintw(status_win, msg, args);
370 status_empty = false;
371 } else {
372 status_empty = true;
374 wclrtoeol(status_win);
375 return true;
378 return false;
381 void
382 update_status(const char *msg, ...)
384 va_list args;
386 va_start(args, msg);
387 update_status_window(display[current_view], msg, args);
388 va_end(args);
391 void
392 report(const char *msg, ...)
394 struct view *view = display[current_view];
395 va_list args;
397 if (!view) {
398 char buf[SIZEOF_STR];
399 int retval;
401 FORMAT_BUFFER(buf, sizeof(buf), msg, retval, true);
402 die("%s", buf);
405 va_start(args, msg);
406 if (update_status_window(view, msg, args))
407 wnoutrefresh(status_win);
408 va_end(args);
410 update_view_title(view);
413 static void
414 done_display(void)
416 if (cursed)
417 endwin();
418 cursed = false;
421 void
422 init_display(void)
424 bool no_display = !!getenv("TIG_NO_DISPLAY");
425 const char *term;
426 int x, y;
428 die_callback = done_display;
429 /* XXX: Restore tty modes and let the OS cleanup the rest! */
430 if (atexit(done_display))
431 die("Failed to register done_display");
433 /* Initialize the curses library */
434 if (!no_display && isatty(STDIN_FILENO)) {
435 cursed = !!initscr();
436 opt_tty = stdin;
437 } else {
438 /* Leave stdin and stdout alone when acting as a pager. */
439 FILE *out_tty;
441 opt_tty = fopen("/dev/tty", "r+");
442 out_tty = no_display ? fopen("/dev/null", "w+") : opt_tty;
443 if (!opt_tty || !out_tty)
444 die("Failed to open /dev/tty");
445 cursed = !!newterm(NULL, out_tty, opt_tty);
448 if (!cursed)
449 die("Failed to initialize curses");
451 nonl(); /* Disable conversion and detect newlines from input. */
452 cbreak(); /* Take input chars one at a time, no wait for \n */
453 noecho(); /* Don't echo input */
454 leaveok(stdscr, false);
456 init_colors();
458 getmaxyx(stdscr, y, x);
459 status_win = newwin(1, x, y - 1, 0);
460 if (!status_win)
461 die("Failed to create status window");
463 /* Enable keyboard mapping */
464 keypad(status_win, true);
465 wbkgdset(status_win, get_line_attr(NULL, LINE_STATUS));
466 enable_mouse(opt_mouse);
468 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
469 set_tabsize(opt_tab_size);
470 #else
471 TABSIZE = opt_tab_size;
472 #endif
474 term = getenv("XTERM_VERSION") ? NULL : getenv("COLORTERM");
475 if (term && !strcmp(term, "gnome-terminal")) {
476 /* In the gnome-terminal-emulator, the warning message
477 * shown when scrolling up one line while the cursor is
478 * on the first line followed by scrolling down one line
479 * corrupts the status line. This is fixed by calling
480 * wclear. */
481 use_scroll_status_wclear = true;
482 use_scroll_redrawwin = false;
484 } else if (term && !strcmp(term, "xrvt-xpm")) {
485 /* No problems with full optimizations in xrvt-(unicode)
486 * and aterm. */
487 use_scroll_status_wclear = use_scroll_redrawwin = false;
489 } else {
490 /* When scrolling in (u)xterm the last line in the
491 * scrolling direction will update slowly. */
492 use_scroll_redrawwin = true;
493 use_scroll_status_wclear = false;
497 static bool
498 read_script(struct key *key)
500 static struct buffer input_buffer;
501 static const char *line = "";
502 enum status_code code;
504 if (!line || !*line) {
505 if (input_buffer.data && *input_buffer.data == ':') {
506 line = "<Enter>";
507 memset(&input_buffer, 0, sizeof(input_buffer));
509 } else if (!io_get(&script_io, &input_buffer, '\n', true)) {
510 io_done(&script_io);
511 return false;
512 } else {
513 line = input_buffer.data;
517 code = get_key_value(&line, key);
518 if (code != SUCCESS)
519 die("Error reading script: %s", get_status_message(code));
520 return true;
524 get_input_char(void)
526 if (is_script_executing()) {
527 static struct key key;
528 static int bytes_pos;
530 if (!key.modifiers.multibytes || bytes_pos >= strlen(key.data.bytes)) {
531 if (!read_script(&key))
532 return 0;
533 bytes_pos = 0;
536 if (!key.modifiers.multibytes) {
537 if (key.data.value < 128)
538 return key.data.value;
539 die("Only ASCII control characters can be used in prompts: %d", key.data.value);
542 return key.data.bytes[bytes_pos++];
545 return getc(opt_tty);
549 get_input(int prompt_position, struct key *key)
551 struct view *view;
552 int i, key_value, cursor_y, cursor_x;
554 if (prompt_position > 0)
555 input_mode = true;
557 memset(key, 0, sizeof(*key));
559 while (true) {
560 int delay = -1;
562 if (opt_refresh_mode == REFRESH_MODE_PERIODIC) {
563 delay = watch_periodic(opt_refresh_interval);
564 foreach_displayed_view (view, i) {
565 if (view_can_refresh(view) &&
566 watch_dirty(&view->watch))
567 refresh_view(view);
571 foreach_view (view, i) {
572 update_view(view);
573 if (view_is_displayed(view) && view->has_scrolled &&
574 use_scroll_redrawwin)
575 redrawwin(view->win);
576 view->has_scrolled = false;
577 if (view->pipe)
578 delay = 0;
581 /* Update the cursor position. */
582 if (prompt_position) {
583 getbegyx(status_win, cursor_y, cursor_x);
584 cursor_x = prompt_position;
585 } else {
586 view = display[current_view];
587 getbegyx(view->win, cursor_y, cursor_x);
588 cursor_x += view->width - 1;
589 cursor_y += view->pos.lineno - view->pos.offset;
591 set_cursor_pos(cursor_y, cursor_x);
593 if (is_script_executing()) {
594 /* Wait for the current command to complete. */
595 if (delay == 0 || !read_script(key))
596 continue;
597 return key->modifiers.multibytes ? OK : key->data.value;
599 } else {
600 /* Refresh, accept single keystroke of input */
601 doupdate();
602 wtimeout(status_win, delay);
603 key_value = wgetch(status_win);
606 /* wgetch() with nodelay() enabled returns ERR when
607 * there's no input. */
608 if (key_value == ERR) {
610 } else if (key_value == KEY_RESIZE) {
611 int height, width;
613 getmaxyx(stdscr, height, width);
615 wresize(status_win, 1, width);
616 mvwin(status_win, height - 1, 0);
617 wnoutrefresh(status_win);
618 resize_display();
619 redraw_display(true);
621 } else {
622 int pos, key_length;
624 input_mode = false;
625 if (key_value == erasechar())
626 key_value = KEY_BACKSPACE;
629 * Ctrl-<key> values are represented using a 0x1F
630 * bitmask on the key value. To 'unmap' we assume that:
632 * - Ctrl-Z is handled by Ncurses.
633 * - Ctrl-m is the same as Return/Enter.
634 * - Ctrl-i is the same as Tab.
636 * For all other key values in the range the Ctrl flag
637 * is set and the key value is updated to the proper
638 * ASCII value.
640 if (KEY_CTL('a') <= key_value && key_value <= KEY_CTL('y') &&
641 key_value != KEY_RETURN && key_value != KEY_TAB) {
642 key->modifiers.control = 1;
643 key_value = key_value | 0x40;
646 if ((key_value >= KEY_MIN && key_value < KEY_MAX) || key_value < 0x1F) {
647 key->data.value = key_value;
648 return key->data.value;
651 key->modifiers.multibytes = 1;
652 key->data.bytes[0] = key_value;
654 key_length = utf8_char_length(key->data.bytes);
655 for (pos = 1; pos < key_length && pos < sizeof(key->data.bytes) - 1; pos++) {
656 key->data.bytes[pos] = wgetch(status_win);
659 return OK;
664 void
665 enable_mouse(bool enable)
667 #ifdef NCURSES_MOUSE_VERSION
668 static bool enabled = false;
670 if (enable != enabled) {
671 mmask_t mask = enable ? ALL_MOUSE_EVENTS : 0;
673 if (mousemask(mask, NULL))
674 mouseinterval(0);
675 enabled = enable;
677 #endif
680 /* vim: set ts=8 sw=8 noexpandtab: */