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.
18 #include "tig/options.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
;
33 static struct io script_io
= { -1 };
36 is_script_executing(void)
38 return script_io
.pipe
!= -1;
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
);
52 open_external_viewer(const char *argv
[], const char *dir
, bool silent
, bool confirm
, bool refresh
, const char *notice
)
57 ok
= io_run_bg(argv
, dir
);
60 def_prog_mode(); /* save current tty modes */
61 endwin(); /* restore original tty modes */
62 ok
= io_run_fg(argv
, dir
);
65 fprintf(stderr
, "%s", notice
);
66 if (!is_script_executing()) {
67 fprintf(stderr
, "Press Enter to continue");
74 if (watch_update(WATCH_EVENT_AFTER_COMMAND
) && refresh
) {
78 foreach_displayed_view (view
, i
) {
79 if (watch_dirty(&view
->watch
))
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"
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
];
104 editor
= getenv("GIT_EDITOR");
105 if (!editor
&& *opt_editor
)
108 editor
= getenv("VISUAL");
110 editor
= getenv("EDITOR");
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");
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;
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
;
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
;
149 vertical_split_is_enabled(void)
151 if (opt_vertical_split
== VERTICAL_SPLIT_AUTO
) {
154 getmaxyx(stdscr
, height
, width
);
155 return width
> 160 || width
* VSPLIT_SCALE
> (height
- 1) * 2;
158 return opt_vertical_split
== VERTICAL_SPLIT_VERTICAL
;
162 redraw_display_separator(bool clear
)
164 if (displayed_views() > 1 && vertical_split_is_enabled()) {
165 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
169 wbkgd(display_sep
, separator
+ get_line_attr(NULL
, LINE_TITLE_BLUR
));
170 wnoutrefresh(display_sep
);
178 struct view
*base
= display
[0];
179 struct view
*view
= display
[1] ? display
[1] : display
[0];
182 /* Setup window dimensions */
184 getmaxyx(stdscr
, base
->height
, base
->width
);
186 /* Make room for the status window. */
189 vsplit
= vertical_split_is_enabled();
193 apply_vertical_split(base
, view
);
195 /* Make room for the separator bar. */
198 apply_horizontal_split(base
, view
);
201 /* Make room for the title bar. */
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. */
213 foreach_displayed_view (view
, i
) {
214 if (!display_win
[i
]) {
215 display_win
[i
] = newwin(view
->height
, view
->width
, y
, x
);
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");
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
) {
234 display_sep
= newwin(view
->height
, 1, 0, x
- 1);
236 die("Failed to create separator window");
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
];
248 x
+= view
->width
+ 1;
250 y
+= view
->height
+ 1;
253 redraw_display_separator(false);
257 redraw_display(bool clear
)
262 foreach_displayed_view (view
, i
) {
266 update_view_title(view
);
269 redraw_display_separator(clear
);
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;
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);
290 return fprintf(file
, "%s\n", buf
) == read1
+ 1 + read2
+ 1;
294 save_display(const char *path
)
299 FILE *file
= fopen(path
, "w");
301 struct view
*view
= display
[0];
306 getmaxyx(stdscr
, i
, width
);
308 line
= malloc(linelen
+ 1);
314 if (view
->width
< width
&& display
[1]) {
315 struct view
*left
= display
[0],
318 for (i
= 0; ok
&& i
< left
->height
; i
++)
319 ok
= save_window_vline(file
, left
->win
, right
->win
, i
, line
, linelen
);
321 ok
= save_window_vline(file
, left
->title
, right
->title
, 0, line
, linelen
);
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
);
329 ok
= save_window_line(file
, view
->title
, 0, line
, linelen
);
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. */
352 /* Reading from the prompt? */
353 static bool input_mode
= false;
355 static bool status_empty
= false;
357 /* Update status and title window. */
359 update_status_window(struct view
*view
, const char *msg
, va_list args
)
364 if (!status_empty
|| *msg
) {
365 wmove(status_win
, 0, 0);
366 if (view
&& view
->has_scrolled
&& use_scroll_status_wclear
)
369 vwprintw(status_win
, msg
, args
);
370 status_empty
= false;
374 wclrtoeol(status_win
);
382 update_status(const char *msg
, ...)
387 update_status_window(display
[current_view
], msg
, args
);
392 report(const char *msg
, ...)
394 struct view
*view
= display
[current_view
];
398 char buf
[SIZEOF_STR
];
401 FORMAT_BUFFER(buf
, sizeof(buf
), msg
, retval
, true);
406 if (update_status_window(view
, msg
, args
))
407 wnoutrefresh(status_win
);
410 update_view_title(view
);
424 bool no_display
= !!getenv("TIG_NO_DISPLAY");
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();
438 /* Leave stdin and stdout alone when acting as a pager. */
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
);
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);
458 getmaxyx(stdscr
, y
, x
);
459 status_win
= newwin(1, x
, y
- 1, 0);
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
);
471 TABSIZE
= opt_tab_size
;
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
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)
487 use_scroll_status_wclear
= use_scroll_redrawwin
= false;
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;
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
== ':') {
507 memset(&input_buffer
, 0, sizeof(input_buffer
));
509 } else if (!io_get(&script_io
, &input_buffer
, '\n', true)) {
513 line
= input_buffer
.data
;
517 code
= get_key_value(&line
, key
);
519 die("Error reading script: %s", get_status_message(code
));
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
))
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
)
552 int i
, key_value
, cursor_y
, cursor_x
;
554 if (prompt_position
> 0)
557 memset(key
, 0, sizeof(*key
));
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
))
571 foreach_view (view
, i
) {
573 if (view_is_displayed(view
) && view
->has_scrolled
&&
574 use_scroll_redrawwin
)
575 redrawwin(view
->win
);
576 view
->has_scrolled
= false;
581 /* Update the cursor position. */
582 if (prompt_position
) {
583 getbegyx(status_win
, cursor_y
, cursor_x
);
584 cursor_x
= prompt_position
;
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
))
597 return key
->modifiers
.multibytes
? OK
: key
->data
.value
;
600 /* Refresh, accept single keystroke of input */
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
) {
613 getmaxyx(stdscr
, height
, width
);
615 wresize(status_win
, 1, width
);
616 mvwin(status_win
, height
- 1, 0);
617 wnoutrefresh(status_win
);
619 redraw_display(true);
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
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
);
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
))
680 /* vim: set ts=8 sw=8 noexpandtab: */