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.
18 #include "tig/options.h"
21 #include "tig/display.h"
23 struct view
*display
[2];
24 unsigned int current_view
;
26 static WINDOW
*display_win
[2];
27 static WINDOW
*display_title
[2];
28 static WINDOW
*display_sep
;
33 open_external_viewer(const char *argv
[], const char *dir
, bool confirm
, const char *notice
)
37 def_prog_mode(); /* save current tty modes */
38 endwin(); /* restore original tty modes */
39 ok
= io_run_fg(argv
, dir
);
42 fprintf(stderr
, "%s", notice
);
43 fprintf(stderr
, "Press Enter to continue");
51 #define EDITOR_LINENO_MSG \
52 "*** Your editor reported an error while opening the file.\n" \
53 "*** This is probably because it doesn't support the line\n" \
54 "*** number argument added automatically. The line number\n" \
55 "*** has been disabled for now. You can permanently disable\n" \
56 "*** it by adding the following line to ~/.tigrc\n" \
57 "*** set editor-line-number = no\n"
60 open_editor(const char *file
, unsigned int lineno
)
62 const char *editor_argv
[SIZEOF_ARG
+ 3] = { "vi", file
, NULL
};
63 char editor_cmd
[SIZEOF_STR
];
64 char lineno_cmd
[SIZEOF_STR
];
68 editor
= getenv("GIT_EDITOR");
69 if (!editor
&& *opt_editor
)
72 editor
= getenv("VISUAL");
74 editor
= getenv("EDITOR");
78 string_ncopy(editor_cmd
, editor
, strlen(editor
));
79 if (!argv_from_string_no_quotes(editor_argv
, &argc
, editor_cmd
)) {
80 report("Failed to read editor command");
84 if (lineno
&& opt_editor_line_number
&& string_format(lineno_cmd
, "+%u", lineno
))
85 editor_argv
[argc
++] = lineno_cmd
;
86 editor_argv
[argc
] = file
;
87 if (!open_external_viewer(editor_argv
, repo
.cdup
, TRUE
, EDITOR_LINENO_MSG
))
88 opt_editor_line_number
= FALSE
;
93 apply_horizontal_split(struct view
*base
, struct view
*view
)
95 view
->width
= base
->width
;
96 view
->height
= apply_step(opt_split_view_height
, base
->height
);
97 view
->height
= MAX(view
->height
, MIN_VIEW_HEIGHT
);
98 view
->height
= MIN(view
->height
, base
->height
- MIN_VIEW_HEIGHT
);
99 base
->height
-= view
->height
;
103 apply_vertical_split(struct view
*base
, struct view
*view
)
105 view
->height
= base
->height
;
106 view
->width
= apply_step(VSPLIT_SCALE
, base
->width
);
107 view
->width
= MAX(view
->width
, MIN_VIEW_WIDTH
);
108 view
->width
= MIN(view
->width
, base
->width
- MIN_VIEW_WIDTH
);
109 base
->width
-= view
->width
;
113 vertical_split_is_enabled(void)
115 if (opt_vertical_split
== VERTICAL_SPLIT_AUTO
) {
118 getmaxyx(stdscr
, height
, width
);
119 return width
> 160 || width
* VSPLIT_SCALE
> (height
- 1) * 2;
122 return opt_vertical_split
== VERTICAL_SPLIT_VERTICAL
;
126 redraw_display_separator(bool clear
)
128 if (displayed_views() > 1 && vertical_split_is_enabled()) {
129 chtype separator
= opt_line_graphics
? ACS_VLINE
: '|';
133 wbkgd(display_sep
, separator
+ get_line_attr(NULL
, LINE_TITLE_BLUR
));
134 wnoutrefresh(display_sep
);
142 struct view
*base
= display
[0];
143 struct view
*view
= display
[1] ? display
[1] : display
[0];
146 /* Setup window dimensions */
148 getmaxyx(stdscr
, base
->height
, base
->width
);
149 string_format(opt_env_columns
, "COLUMNS=%d", base
->width
);
150 string_format(opt_env_lines
, "LINES=%d", base
->height
);
152 /* Make room for the status window. */
155 vsplit
= vertical_split_is_enabled();
159 apply_vertical_split(base
, view
);
161 /* Make room for the separator bar. */
164 apply_horizontal_split(base
, view
);
167 /* Make room for the title bar. */
171 /* Make room for the title bar. */
176 foreach_displayed_view (view
, i
) {
177 if (!display_win
[i
]) {
178 display_win
[i
] = newwin(view
->height
, view
->width
, y
, x
);
180 die("Failed to create %s view", view
->name
);
182 scrollok(display_win
[i
], FALSE
);
184 display_title
[i
] = newwin(1, view
->width
, y
+ view
->height
, x
);
185 if (!display_title
[i
])
186 die("Failed to create title window");
189 wresize(display_win
[i
], view
->height
, view
->width
);
190 mvwin(display_win
[i
], y
, x
);
191 wresize(display_title
[i
], 1, view
->width
);
192 mvwin(display_title
[i
], y
+ view
->height
, x
);
195 if (i
> 0 && vsplit
) {
197 display_sep
= newwin(view
->height
, 1, 0, x
- 1);
199 die("Failed to create separator window");
202 wresize(display_sep
, view
->height
, 1);
203 mvwin(display_sep
, 0, x
- 1);
207 view
->win
= display_win
[i
];
208 view
->title
= display_title
[i
];
211 x
+= view
->width
+ 1;
213 y
+= view
->height
+ 1;
216 redraw_display_separator(FALSE
);
220 redraw_display(bool clear
)
225 foreach_displayed_view (view
, i
) {
229 update_view_title(view
);
232 redraw_display_separator(clear
);
239 /* Whether or not the curses interface has been initialized. */
240 static bool cursed
= FALSE
;
242 /* Terminal hacks and workarounds. */
243 static bool use_scroll_redrawwin
;
244 static bool use_scroll_status_wclear
;
246 /* The status window is used for polling keystrokes. */
247 static WINDOW
*status_win
;
249 /* Reading from the prompt? */
250 static bool input_mode
= FALSE
;
252 static bool status_empty
= FALSE
;
254 /* Update status and title window. */
256 report(const char *msg
, ...)
258 struct view
*view
= display
[current_view
];
264 char buf
[SIZEOF_STR
];
267 FORMAT_BUFFER(buf
, sizeof(buf
), msg
, retval
, TRUE
);
271 if (!status_empty
|| *msg
) {
276 wmove(status_win
, 0, 0);
277 if (view
->has_scrolled
&& use_scroll_status_wclear
)
280 vwprintw(status_win
, msg
, args
);
281 status_empty
= FALSE
;
285 wclrtoeol(status_win
);
286 wnoutrefresh(status_win
);
291 update_view_title(view
);
306 die_callback
= done_display
;
307 /* XXX: Restore tty modes and let the OS cleanup the rest! */
308 if (atexit(done_display
))
309 die("Failed to register done_display");
311 /* Initialize the curses library */
312 if (isatty(STDIN_FILENO
)) {
313 cursed
= !!initscr();
316 /* Leave stdin and stdout alone when acting as a pager. */
317 opt_tty
= fopen("/dev/tty", "r+");
319 die("Failed to open /dev/tty");
320 cursed
= !!newterm(NULL
, opt_tty
, opt_tty
);
324 die("Failed to initialize curses");
326 nonl(); /* Disable conversion and detect newlines from input. */
327 cbreak(); /* Take input chars one at a time, no wait for \n */
328 noecho(); /* Don't echo input */
329 leaveok(stdscr
, FALSE
);
334 getmaxyx(stdscr
, y
, x
);
335 status_win
= newwin(1, x
, y
- 1, 0);
337 die("Failed to create status window");
339 /* Enable keyboard mapping */
340 keypad(status_win
, TRUE
);
341 wbkgdset(status_win
, get_line_attr(NULL
, LINE_STATUS
));
342 #ifdef NCURSES_MOUSE_VERSION
345 mousemask(ALL_MOUSE_EVENTS
, NULL
);
350 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH >= 20080119)
351 set_tabsize(opt_tab_size
);
353 TABSIZE
= opt_tab_size
;
356 term
= getenv("XTERM_VERSION") ? NULL
: getenv("COLORTERM");
357 if (term
&& !strcmp(term
, "gnome-terminal")) {
358 /* In the gnome-terminal-emulator, the message from
359 * scrolling up one line when impossible followed by
360 * scrolling down one line causes corruption of the
361 * status line. This is fixed by calling wclear. */
362 use_scroll_status_wclear
= TRUE
;
363 use_scroll_redrawwin
= FALSE
;
365 } else if (term
&& !strcmp(term
, "xrvt-xpm")) {
366 /* No problems with full optimizations in xrvt-(unicode)
368 use_scroll_status_wclear
= use_scroll_redrawwin
= FALSE
;
371 /* When scrolling in (u)xterm the last line in the
372 * scrolling direction will update slowly. */
373 use_scroll_redrawwin
= TRUE
;
374 use_scroll_status_wclear
= FALSE
;
379 get_input(int prompt_position
, struct key_input
*input
, bool modifiers
)
382 int i
, key
, cursor_y
, cursor_x
;
387 memset(input
, 0, sizeof(*input
));
390 bool loading
= FALSE
;
392 foreach_view (view
, i
) {
394 if (view_is_displayed(view
) && view
->has_scrolled
&&
395 use_scroll_redrawwin
)
396 redrawwin(view
->win
);
397 view
->has_scrolled
= FALSE
;
402 /* Update the cursor position. */
403 if (prompt_position
) {
404 getbegyx(status_win
, cursor_y
, cursor_x
);
405 cursor_x
= prompt_position
;
407 view
= display
[current_view
];
408 getbegyx(view
->win
, cursor_y
, cursor_x
);
409 cursor_x
= view
->width
- 1;
410 cursor_y
+= view
->pos
.lineno
- view
->pos
.offset
;
412 setsyx(cursor_y
, cursor_x
);
414 /* Refresh, accept single keystroke of input */
416 nodelay(status_win
, loading
);
417 key
= wgetch(status_win
);
419 /* wgetch() with nodelay() enabled returns ERR when
420 * there's no input. */
423 } else if (key
== KEY_ESC
&& modifiers
) {
424 input
->modifiers
.escape
= 1;
426 } else if (key
== KEY_RESIZE
) {
429 getmaxyx(stdscr
, height
, width
);
431 wresize(status_win
, 1, width
);
432 mvwin(status_win
, height
- 1, 0);
433 wnoutrefresh(status_win
);
435 redraw_display(TRUE
);
441 if (key
== erasechar())
445 * Ctrl-<key> values are represented using a 0x1F
446 * bitmask on the key value. To 'unmap' we assume that:
448 * - Ctrl-Z is handled by Ncurses.
449 * - Ctrl-m is the same as Return/Enter.
450 * - Ctrl-i is the same as Tab.
452 * For all other key values in the range the Ctrl flag
453 * is set and the key value is updated to the proper
456 if (KEY_CTL('a') <= key
&& key
<= KEY_CTL('x') && key
!= KEY_RETURN
&& key
!= KEY_TAB
) {
457 input
->modifiers
.control
= 1;
461 if ((key
>= KEY_MIN
&& key
< KEY_MAX
) || key
< 0x1F) { // || key == ' ') {
462 input
->data
.key
= key
;
463 return input
->data
.key
;
466 input
->modifiers
.multibytes
= 1;
467 input
->data
.bytes
[0] = key
;
469 key_length
= utf8_char_length(input
->data
.bytes
);
470 for (pos
= 1; pos
< key_length
&& pos
< sizeof(input
->data
.bytes
) - 1; pos
++) {
471 input
->data
.bytes
[pos
] = wgetch(status_win
);
479 /* vim: set ts=8 sw=8 noexpandtab: */