1 /* vifm
2  * Copyright (C) 2011 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "view.h"
20 
21 #include <curses.h>
22 
23 #include <regex.h>
24 #include <unistd.h> /* usleep() */
25 
26 #include <assert.h> /* assert() */
27 #include <stddef.h> /* ptrdiff_t size_t */
28 #include <string.h> /* memset() strdup() */
29 #include <stdio.h>  /* fclose() snprintf() */
30 #include <stdlib.h> /* free() */
31 
32 #include "../cfg/config.h"
33 #include "../compat/curses.h"
34 #include "../compat/fs_limits.h"
35 #include "../compat/os.h"
36 #include "../compat/reallocarray.h"
37 #include "../engine/keys.h"
38 #include "../engine/mode.h"
39 #include "../int/vim.h"
40 #include "../modes/dialogs/msg_dialog.h"
41 #include "../ui/cancellation.h"
42 #include "../ui/color_manager.h"
43 #include "../ui/colors.h"
44 #include "../ui/escape.h"
45 #include "../ui/fileview.h"
46 #include "../ui/quickview.h"
47 #include "../ui/statusbar.h"
48 #include "../ui/ui.h"
49 #include "../utils/filemon.h"
50 #include "../utils/fs.h"
51 #include "../utils/macros.h"
52 #include "../utils/path.h"
53 #include "../utils/regexp.h"
54 #include "../utils/str.h"
55 #include "../utils/string_array.h"
56 #include "../utils/utf8.h"
57 #include "../utils/utils.h"
58 #include "../filelist.h"
59 #include "../filetype.h"
60 #include "../running.h"
61 #include "../status.h"
62 #include "../types.h"
63 #include "cmdline.h"
64 #include "modes.h"
65 #include "normal.h"
66 #include "wk.h"
67 
68 /* Named boolean values of "silent" parameter for better readability. */
69 enum
70 {
71 	NOSILENT, /* Display error message dialog. */
72 	SILENT,   /* Do not display error message dialog. */
73 };
74 
75 /* Describes view state and its properties. */
76 struct modview_info_t
77 {
78 	/* Data of the view. */
79 	char **lines;     /* List of real lines. */
80 	int (*widths)[2]; /* (virtual line, screen width) pair per real line. */
81 	int nlines;       /* Number of real lines. */
82 	int nlinesv;      /* Number of virtual (possibly wrapped) lines. */
83 	int line;         /* Current real line number. */
84 	int linev;        /* Current virtual line number. */
85 
86 	/* Dimensions, units of actions. */
87 	int win_size; /* Scroll window size. */
88 	int half_win; /* Height of a "page" (can be changed). */
89 	int width;    /* Last width used for breaking lines. */
90 
91 	/* Monitoring of changes for automatic forwarding. */
92 	int auto_forward;   /* Whether auto forwarding (tail -F) is enabled. */
93 	filemon_t file_mon; /* File monitor for auto forwarding mode. */
94 
95 	/* Related to search. */
96 	regex_t re;               /* Search regular expression. */
97 	int last_search_backward; /* Value -1 means no search was performed. */
98 	int search_repeat;        /* Saved count prefix of search commands. */
99 
100 	/* The rest of the state. */
101 	view_t *view;    /* File view association with the view. */
102 	char *filename;  /* Full path to the file being viewed. */
103 	char *viewer;    /* When non-NULL, specifies custom preview command (no
104 	                    implicit %c). */
105 	int detached;    /* Whether view mode was detached. */
106 	ViewerKind kind; /* Kind of preview. */
107 	int wrap;        /* Whether lines are wrapped. */
108 };
109 
110 /* View information structure indexes and count. */
111 enum
112 {
113 	VI_LWIN,  /* Index of view information structure for left window. */
114 	VI_RWIN,  /* Index of view information structure for right window. */
115 	VI_COUNT, /* Number of view information structures. */
116 };
117 
118 static int try_resurrect_detached(const char full_path[], int explore);
119 static void try_redraw_explore_view(const view_t *view);
120 static void reset_view_info(modview_info_t *vi);
121 static void init_view_info(modview_info_t *vi);
122 static void free_view_info(modview_info_t *vi);
123 static void redraw(void);
124 static void calc_vlines(void);
125 static void calc_vlines_wrapped(modview_info_t *vi);
126 static void calc_vlines_non_wrapped(modview_info_t *vi);
127 static void draw(void);
128 static int get_part(const char line[], int offset, size_t max_len, char part[]);
129 static void display_error(const char error_msg[]);
130 static void cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info);
131 static void cmd_ctrl_wH(key_info_t key_info, keys_info_t *keys_info);
132 static void cmd_ctrl_wJ(key_info_t key_info, keys_info_t *keys_info);
133 static void cmd_ctrl_wK(key_info_t key_info, keys_info_t *keys_info);
134 static void cmd_ctrl_wL(key_info_t key_info, keys_info_t *keys_info);
135 static view_t * get_active_view(void);
136 static void cmd_ctrl_wb(key_info_t key_info, keys_info_t *keys_info);
137 static void cmd_ctrl_wh(key_info_t key_info, keys_info_t *keys_info);
138 static void cmd_ctrl_wj(key_info_t key_info, keys_info_t *keys_info);
139 static void cmd_ctrl_wk(key_info_t key_info, keys_info_t *keys_info);
140 static void cmd_ctrl_wl(key_info_t key_info, keys_info_t *keys_info);
141 static void cmd_ctrl_wo(key_info_t key_info, keys_info_t *keys_info);
142 static void cmd_ctrl_ws(key_info_t key_info, keys_info_t *keys_info);
143 static void cmd_ctrl_wt(key_info_t key_info, keys_info_t *keys_info);
144 static int is_right_or_bottom(void);
145 static int is_top_or_left(void);
146 static void cmd_ctrl_wv(key_info_t key_info, keys_info_t *keys_info);
147 static void cmd_ctrl_ww(key_info_t key_info, keys_info_t *keys_info);
148 static void cmd_ctrl_wx(key_info_t key_info, keys_info_t *keys_info);
149 static void cmd_ctrl_wz(key_info_t key_info, keys_info_t *keys_info);
150 static void cmd_meta_space(key_info_t key_info, keys_info_t *keys_info);
151 static void cmd_percent(key_info_t key_info, keys_info_t *keys_info);
152 static void cmd_tab(key_info_t key_info, keys_info_t *keys_info);
153 static void cmd_slash(key_info_t key_info, keys_info_t *keys_info);
154 static void pick_vi(int explore);
155 static void cmd_qmark(key_info_t key_info, keys_info_t *keys_info);
156 static void cmd_F(key_info_t key_info, keys_info_t *keys_info);
157 static void cmd_G(key_info_t key_info, keys_info_t *keys_info);
158 static void cmd_N(key_info_t key_info, keys_info_t *keys_info);
159 static void cmd_R(key_info_t key_info, keys_info_t *keys_info);
160 static int load_view_data(modview_info_t *vi, const char action[],
161 		const char file_to_view[], int silent);
162 static int get_view_data(modview_info_t *vi, const char file_to_view[]);
163 static void replace_vi(modview_info_t *orig, modview_info_t *new);
164 static void cmd_b(key_info_t key_info, keys_info_t *keys_info);
165 static void cmd_d(key_info_t key_info, keys_info_t *keys_info);
166 static void cmd_f(key_info_t key_info, keys_info_t *keys_info);
167 static void check_and_set_from_default_win(key_info_t *key_info);
168 static void set_from_default_win(key_info_t *key_info);
169 static void cmd_g(key_info_t key_info, keys_info_t *keys_info);
170 static void cmd_j(key_info_t key_info, keys_info_t *keys_info);
171 static void cmd_k(key_info_t key_info, keys_info_t *keys_info);
172 static void cmd_n(key_info_t key_info, keys_info_t *keys_info);
173 static void goto_search_result(int repeat_count, int inverse_direction);
174 static void search(int repeat_count, int backward);
175 static void find_previous(int vline_offset);
176 static void find_next(void);
177 static void cmd_q(key_info_t key_info, keys_info_t *keys_info);
178 static void cmd_u(key_info_t key_info, keys_info_t *keys_info);
179 static void update_with_half_win(key_info_t *key_info);
180 static void cmd_v(key_info_t key_info, keys_info_t *keys_info);
181 static void cmd_w(key_info_t key_info, keys_info_t *keys_info);
182 static void cmd_z(key_info_t key_info, keys_info_t *keys_info);
183 static void update_with_win(key_info_t *key_info);
184 static int is_trying_the_same_file(void);
185 static int get_file_to_explore(const view_t *view, char buf[], size_t buf_len);
186 static int forward_if_changed(modview_info_t *vi);
187 static int scroll_to_bottom(modview_info_t *vi);
188 static void reload_view(modview_info_t *vi, int silent);
189 static void cleanup(modview_info_t *vi);
190 static modview_info_t * view_info_alloc(void);
191 
192 /* Points to current (for quick view) or last used (for explore mode)
193  * modview_info_t structure. */
194 static modview_info_t *vi;
195 
196 static keys_add_info_t builtin_cmds[] = {
197 	{WK_C_b,           {{&cmd_b},      .descr = "scroll page up"}},
198 	{WK_C_d,           {{&cmd_d},      .descr = "scroll half-page down"}},
199 	{WK_C_e,           {{&cmd_j},      .descr = "scroll one line down"}},
200 	{WK_C_f,           {{&cmd_f},      .descr = "scroll page down"}},
201 	{WK_C_i,           {{&cmd_tab},    .descr = "quit preview/switch pane"}},
202 	{WK_C_k,           {{&cmd_k},      .descr = "scroll one line up"}},
203 	{WK_C_l,           {{&cmd_ctrl_l}, .descr = "redraw"}},
204 	{WK_CR,            {{&cmd_j},      .descr = "scroll one line down"}},
205 	{WK_C_n,           {{&cmd_j},      .descr = "scroll one line down"}},
206 	{WK_C_p,           {{&cmd_k},      .descr = "scroll one line up"}},
207 	{WK_C_r,           {{&cmd_ctrl_l}, .descr = "redraw"}},
208 	{WK_C_u,           {{&cmd_u},      .descr = "undo file operation"}},
209 	{WK_C_v,           {{&cmd_f},      .descr = "scroll page down"}},
210 	{WK_C_w WK_H,      {{&cmd_ctrl_wH}, .descr = "move window to the left"}},
211 	{WK_C_w WK_J,      {{&cmd_ctrl_wJ}, .descr = "move window to the bottom"}},
212 	{WK_C_w WK_K,      {{&cmd_ctrl_wK}, .descr = "move window to the top"}},
213 	{WK_C_w WK_L,      {{&cmd_ctrl_wL}, .descr = "move window to the right"}},
214 	{WK_C_w WK_b,      {{&cmd_ctrl_wb}, .descr = "go to bottom-right window"}},
215 	{WK_C_w WK_C_h,    {{&cmd_ctrl_wh}, .descr = "go to left window"}},
216 	{WK_C_w WK_h,      {{&cmd_ctrl_wh}, .descr = "go to left window"}},
217 	{WK_C_w WK_C_j,    {{&cmd_ctrl_wj}, .descr = "go to bottom window"}},
218 	{WK_C_w WK_j,      {{&cmd_ctrl_wj}, .descr = "go to bottom window"}},
219 	{WK_C_w WK_C_k,    {{&cmd_ctrl_wk}, .descr = "go to top window"}},
220 	{WK_C_w WK_k,      {{&cmd_ctrl_wk}, .descr = "go to top window"}},
221 	{WK_C_w WK_C_l,    {{&cmd_ctrl_wl}, .descr = "go to right window"}},
222 	{WK_C_w WK_l,      {{&cmd_ctrl_wl}, .descr = "go to right window"}},
223 	{WK_C_w WK_C_o,    {{&cmd_ctrl_wo}, .descr = "single-pane mode"}},
224 	{WK_C_w WK_o,      {{&cmd_ctrl_wo}, .descr = "single-pane mode"}},
225 	{WK_C_w WK_C_p,    {{&cmd_ctrl_ww}, .descr = "go to other pane"}},
226 	{WK_C_w WK_p,      {{&cmd_ctrl_ww}, .descr = "go to other pane"}},
227 	{WK_C_w WK_C_t,    {{&cmd_ctrl_wt}, .descr = "go to top-left window"}},
228 	{WK_C_w WK_t,      {{&cmd_ctrl_wt}, .descr = "go to top-left window"}},
229 	{WK_C_w WK_C_w,    {{&cmd_ctrl_ww}, .descr = "go to other pane"}},
230 	{WK_C_w WK_w,      {{&cmd_ctrl_ww}, .descr = "go to other pane"}},
231 	{WK_C_w WK_C_x,    {{&cmd_ctrl_wx}, .descr = "exchange panes"}},
232 	{WK_C_w WK_x,      {{&cmd_ctrl_wx}, .descr = "exchange panes"}},
233 	{WK_C_w WK_C_z,    {{&cmd_ctrl_wz}, .descr = "exit preview/view modes"}},
234 	{WK_C_w WK_z,      {{&cmd_ctrl_wz}, .descr = "exit preview/view modes"}},
235 	{WK_C_w WK_C_s,    {{&cmd_ctrl_ws}, .descr = "horizontal split layout"}},
236 	{WK_C_w WK_s,      {{&cmd_ctrl_ws}, .descr = "horizontal split layout"}},
237 	{WK_C_w WK_C_v,    {{&cmd_ctrl_wv}, .descr = "vertical split layout"}},
238 	{WK_C_w WK_v,      {{&cmd_ctrl_wv}, .descr = "vertical split layout"}},
239 	{WK_C_w WK_EQUALS, {{&modnorm_ctrl_wequal},   .nim = 1, .descr = "size panes equally"}},
240 	{WK_C_w WK_LT,     {{&modnorm_ctrl_wless},    .nim = 1, .descr = "decrease pane size by one"}},
241 	{WK_C_w WK_GT,     {{&modnorm_ctrl_wgreater}, .nim = 1, .descr = "increase pane size by one"}},
242 	{WK_C_w WK_PLUS,   {{&modnorm_ctrl_wplus},    .nim = 1, .descr = "increase pane size by one"}},
243 	{WK_C_w WK_MINUS,  {{&modnorm_ctrl_wminus},   .nim = 1, .descr = "decrease pane size by one"}},
244 	{WK_C_w WK_BAR,    {{&modnorm_ctrl_wpipe},    .nim = 1, .descr = "maximize pane size"}},
245 	{WK_C_w WK_USCORE, {{&modnorm_ctrl_wpipe},    .nim = 1, .descr = "maximize pane size"}},
246 	{WK_C_y,           {{&cmd_k},     .descr = "scroll one line up"}},
247 	{WK_SLASH,         {{&cmd_slash}, .descr = "search forward"}},
248 	{WK_QM,            {{&cmd_qmark}, .descr = "search backward"}},
249 	{WK_LT,            {{&cmd_g}, .descr = "scroll to the beginning"}},
250 	{WK_GT,            {{&cmd_G}, .descr = "scroll to the end"}},
251 	{WK_SPACE,         {{&cmd_f}, .descr = "scroll page down"}},
252 	{WK_ALT WK_LT,     {{&cmd_g}, .descr = "scroll to the beginning"}},
253 	{WK_ALT WK_GT,     {{&cmd_G}, .descr = "scroll to the end"}},
254 	{WK_ALT WK_SPACE,  {{&cmd_meta_space}, .descr = "scroll down one window"}},
255 	{WK_PERCENT,       {{&cmd_percent},    .descr = "to [count]% position"}},
256 	{WK_F,             {{&cmd_F}, .descr = "toggle automatic forward scroll"}},
257 	{WK_G,             {{&cmd_G}, .descr = "scroll to the end"}},
258 	{WK_N,             {{&cmd_N}, .descr = "go to previous search match"}},
259 	{WK_Q,             {{&cmd_q}, .descr = "leave view mode"}},
260 	{WK_R,             {{&cmd_R}, .descr = "reload view contents"}},
261 	{WK_Z WK_Q,        {{&cmd_q}, .descr = "leave view mode"}},
262 	{WK_Z WK_Z,        {{&cmd_q}, .descr = "leave view mode"}},
263 	{WK_b,             {{&cmd_b}, .descr = "scroll page up"}},
264 	{WK_d,             {{&cmd_d}, .descr = "scroll half-page down"}},
265 	{WK_f,             {{&cmd_f}, .descr = "scroll page down"}},
266 	{WK_g,             {{&cmd_g}, .descr = "scroll to the beginning"}},
267 	{WK_e,             {{&cmd_j}, .descr = "scroll one line down"}},
268 	{WK_j,             {{&cmd_j}, .descr = "scroll one line down"}},
269 	{WK_k,             {{&cmd_k}, .descr = "scroll one line up"}},
270 	{WK_n,             {{&cmd_n}, .descr = "go to next search match"}},
271 	{WK_p,             {{&cmd_percent}, .descr = "to [count]% position"}},
272 	{WK_q,             {{&cmd_q},       .descr = "leave view mode"}},
273 	{WK_r,             {{&cmd_ctrl_l},  .descr = "redraw"}},
274 	{WK_u,             {{&cmd_u},       .descr = "undo file operation"}},
275 	{WK_v,             {{&cmd_v},       .descr = "edit file at current line"}},
276 	{WK_y,             {{&cmd_k},       .descr = "scroll one line up"}},
277 	{WK_w,             {{&cmd_w},       .descr = "scroll backward one window"}},
278 	{WK_z,             {{&cmd_z},       .descr = "scroll forward one window"}},
279 #ifndef __PDCURSES__
280 	{WK_ALT WK_v,      {{&cmd_b}, .descr = "scroll page up"}},
281 #else
282 	{{K(ALT_V)},       {{&cmd_b}, .descr = "scroll page up"}},
283 #endif
284 #ifdef ENABLE_EXTENDED_KEYS
285 	{{WC_C_w, K(KEY_BACKSPACE)}, {{&cmd_ctrl_wh}, .descr = "go to left window"}},
286 	{{K(KEY_PPAGE)},             {{&cmd_b}, .descr = "scroll page up"}},
287 	{{K(KEY_NPAGE)},             {{&cmd_f}, .descr = "scroll page down"}},
288 	{{K(KEY_DOWN)},              {{&cmd_j}, .descr = "scroll one line down"}},
289 	{{K(KEY_UP)},                {{&cmd_k}, .descr = "scroll one line up"}},
290 	{{K(KEY_HOME)},              {{&cmd_g}, .descr = "scroll to the beginning"}},
291 	{{K(KEY_END)},               {{&cmd_G}, .descr = "scroll to the end"}},
292 	{{K(KEY_BTAB)},              {{&cmd_q}, .descr = "leave view mode"}},
293 #else
294 	{WK_ESC L"[Z",               {{&cmd_q}, .descr = "leave view mode"}},
295 #endif /* ENABLE_EXTENDED_KEYS */
296 };
297 
298 void
modview_init(void)299 modview_init(void)
300 {
301 	int ret_code;
302 
303 	ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), VIEW_MODE);
304 	assert(ret_code == 0);
305 
306 	(void)ret_code;
307 }
308 
309 void
modview_enter(view_t * view,int explore)310 modview_enter(view_t *view, int explore)
311 {
312 	char full_path[PATH_MAX + 1];
313 
314 	if(get_file_to_explore(curr_view, full_path, sizeof(full_path)) != 0)
315 	{
316 		show_error_msg("File exploring", "The file cannot be explored");
317 		return;
318 	}
319 
320 	/* Either make use of detached view or prune it. */
321 	if(try_resurrect_detached(full_path, explore) == 0)
322 	{
323 		ui_views_update_titles();
324 		return;
325 	}
326 
327 	pick_vi(explore);
328 
329 	vi->view = view;
330 	vi->filename = strdup(full_path);
331 
332 	if(load_view_data(vi, "File exploring", full_path, NOSILENT) != 0)
333 	{
334 		update_string(&vi->filename, NULL);
335 		return;
336 	}
337 
338 	vle_mode_set(VIEW_MODE, VMT_SECONDARY);
339 
340 	if(explore)
341 	{
342 		vi->view = curr_view;
343 		curr_view->explore_mode = 1;
344 	}
345 	else
346 	{
347 		vi->view = other_view;
348 	}
349 
350 	ui_views_update_titles();
351 	modview_redraw();
352 }
353 
354 void
modview_detached_make(view_t * view,const char cmd[])355 modview_detached_make(view_t *view, const char cmd[])
356 {
357 	char full_path[PATH_MAX + 1];
358 
359 	if(get_file_to_explore(curr_view, full_path, sizeof(full_path)) != 0)
360 	{
361 		show_error_msg("File exploring", "The file cannot be explored");
362 		return;
363 	}
364 
365 	pick_vi(0);
366 	reset_view_info(vi);
367 
368 	vi->view = view;
369 	vi->viewer = strdup(cmd);
370 	vi->filename = strdup(full_path);
371 	vi->detached = 1;
372 
373 	if(load_view_data(vi, "File viewing", full_path, NOSILENT) != 0)
374 	{
375 		reset_view_info(vi);
376 		return;
377 	}
378 
379 	ui_views_update_titles();
380 	modview_redraw();
381 }
382 
383 /* Either makes use of detached view or prunes it.  Returns zero on success,
384  * otherwise non-zero is returned. */
385 static int
try_resurrect_detached(const char full_path[],int explore)386 try_resurrect_detached(const char full_path[], int explore)
387 {
388 	const int same_file = vi != NULL
389 	                   && vi->detached
390 	                   && vi->view == (explore ? curr_view : other_view)
391 	                   && vi->filename != NULL
392 	                   && stroscmp(vi->filename, full_path) == 0;
393 	if(!same_file)
394 	{
395 		return 1;
396 	}
397 
398 	if(explore)
399 	{
400 		vi->view->explore_mode = 0;
401 		reset_view_info(vi);
402 		return 1;
403 	}
404 	else
405 	{
406 		vle_mode_set(VIEW_MODE, VMT_SECONDARY);
407 		return 0;
408 	}
409 }
410 
411 void
modview_try_activate(void)412 modview_try_activate(void)
413 {
414 	if(curr_view->explore_mode)
415 	{
416 		vle_mode_set(VIEW_MODE, VMT_SECONDARY);
417 		pick_vi(curr_view->explore_mode);
418 	}
419 }
420 
421 void
modview_pre(void)422 modview_pre(void)
423 {
424 	if(curr_stats.save_msg == 0)
425 	{
426 		const char *const suffix = vi->auto_forward ? "(auto forwarding)" : "";
427 		ui_sb_msgf("-- VIEW -- %s", suffix);
428 		curr_stats.save_msg = 2;
429 	}
430 }
431 
432 void
modview_post(void)433 modview_post(void)
434 {
435 	update_screen(stats_update_fetch());
436 	modview_ruler_update();
437 }
438 
439 void
modview_ruler_update(void)440 modview_ruler_update(void)
441 {
442 	char buf[32];
443 	snprintf(buf, sizeof(buf), "%d-%d ", vi->line + 1, vi->nlines);
444 
445 	ui_ruler_set(buf);
446 }
447 
448 void
modview_redraw(void)449 modview_redraw(void)
450 {
451 	modview_info_t *saved_vi = vi;
452 
453 	try_redraw_explore_view(&lwin);
454 	try_redraw_explore_view(&rwin);
455 
456 	if(!lwin.explore_mode && !rwin.explore_mode)
457 	{
458 		redraw();
459 	}
460 
461 	vi = saved_vi;
462 }
463 
464 /* Redraws view in explore mode if view is really in explore mode and is visible
465  * on the screen. */
466 static void
try_redraw_explore_view(const view_t * view)467 try_redraw_explore_view(const view_t *view)
468 {
469 	if(view->explore_mode && ui_view_is_visible(view))
470 	{
471 		vi = view->vi;
472 		redraw();
473 	}
474 }
475 
476 void
modview_leave(void)477 modview_leave(void)
478 {
479 	if(vi->kind != VK_TEXTUAL && vi->view->explore_mode)
480 	{
481 		cleanup(vi);
482 	}
483 
484 	reset_view_info(vi);
485 
486 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
487 
488 	if(curr_view->explore_mode)
489 	{
490 		curr_view->explore_mode = 0;
491 		redraw_current_view();
492 	}
493 	else
494 	{
495 		qv_draw(curr_view);
496 	}
497 
498 	ui_view_title_update(curr_view);
499 
500 	if(curr_view->explore_mode || other_view->explore_mode)
501 	{
502 		modview_redraw();
503 	}
504 }
505 
506 void
modview_quit_exploring(view_t * view)507 modview_quit_exploring(view_t *view)
508 {
509 	assert(!vle_mode_is(VIEW_MODE) && "Unexpected mode.");
510 	if(!view->explore_mode)
511 	{
512 		return;
513 	}
514 
515 	view->explore_mode = 0;
516 
517 	reset_view_info(view->vi);
518 
519 	redraw_view(view);
520 	ui_view_title_update(view);
521 }
522 
523 /* Frees and initializes anew modview_info_t structure instance. */
524 static void
reset_view_info(modview_info_t * vi)525 reset_view_info(modview_info_t *vi)
526 {
527 	free_view_info(vi);
528 	init_view_info(vi);
529 }
530 
531 /* Initializes modview_info_t structure instance with safe default values. */
532 static void
init_view_info(modview_info_t * vi)533 init_view_info(modview_info_t *vi)
534 {
535 	memset(vi, '\0', sizeof(*vi));
536 	vi->win_size = -1;
537 	vi->half_win = -1;
538 	vi->width = -1;
539 	vi->last_search_backward = -1;
540 	vi->search_repeat = NO_COUNT_GIVEN;
541 	vi->nlines = 0;
542 	vi->lines = NULL;
543 	vi->widths = NULL;
544 	vi->filename = NULL;
545 	vi->viewer = NULL;
546 }
547 
548 /* Frees all resources allocated by modview_info_t structure instance. */
549 static void
free_view_info(modview_info_t * vi)550 free_view_info(modview_info_t *vi)
551 {
552 	free_string_array(vi->lines, vi->nlines);
553 	free(vi->widths);
554 	if(vi->last_search_backward != -1)
555 	{
556 		regfree(&vi->re);
557 	}
558 	free(vi->filename);
559 	free(vi->viewer);
560 }
561 
562 /* Updates line width and redraws the view. */
563 static void
redraw(void)564 redraw(void)
565 {
566 	ui_view_title_update(vi->view);
567 	calc_vlines();
568 	draw();
569 }
570 
571 /* Recalculates virtual lines of a view if display options require it. */
572 static void
calc_vlines(void)573 calc_vlines(void)
574 {
575 	/* Skip the recalculation if window size and wrapping options are the same. */
576 	if(ui_qv_width(vi->view) == vi->width && vi->wrap == cfg.wrap_quick_view)
577 	{
578 		return;
579 	}
580 
581 	vi->width = ui_qv_width(vi->view);
582 	vi->wrap = cfg.wrap_quick_view;
583 
584 	if(vi->wrap)
585 	{
586 		calc_vlines_wrapped(vi);
587 	}
588 	else
589 	{
590 		calc_vlines_non_wrapped(vi);
591 	}
592 }
593 
594 /* Recalculates virtual lines of a view with line wrapping. */
595 static void
calc_vlines_wrapped(modview_info_t * vi)596 calc_vlines_wrapped(modview_info_t *vi)
597 {
598 	int i;
599 	vi->nlinesv = 0;
600 	for(i = 0; i < vi->nlines; i++)
601 	{
602 		vi->widths[i][0] = vi->nlinesv++;
603 		vi->widths[i][1] = utf8_strsw_with_tabs(vi->lines[i], cfg.tab_stop) -
604 			esc_str_overhead(vi->lines[i]);
605 		vi->nlinesv += vi->widths[i][1]/vi->width;
606 	}
607 }
608 
609 /* Recalculates virtual lines of a view without line wrapping. */
610 static void
calc_vlines_non_wrapped(modview_info_t * vi)611 calc_vlines_non_wrapped(modview_info_t *vi)
612 {
613 	int i;
614 	vi->nlinesv = vi->nlines;
615 	for(i = 0; i < vi->nlines; i++)
616 	{
617 		vi->widths[i][0] = i;
618 		vi->widths[i][1] = vi->width;
619 	}
620 }
621 
622 static void
draw(void)623 draw(void)
624 {
625 	int l, vl;
626 	const int height = ui_qv_height(vi->view);
627 	const int width = ui_qv_width(vi->view);
628 	const int max_l = MIN(vi->line + height, vi->nlines);
629 	const int searched = (vi->last_search_backward != -1);
630 	esc_state state;
631 
632 	if(vi->kind != VK_TEXTUAL)
633 	{
634 		cleanup(vi);
635 
636 		free_string_array(vi->lines, vi->nlines);
637 		(void)get_view_data(vi, vi->filename);
638 
639 		if(vi->kind == VK_PASS_THROUGH)
640 		{
641 			strlist_t list = { .nitems = vi->nlines, .items = vi->lines };
642 			ui_pass_through(&list, vi->view->win, ui_qv_left(vi->view),
643 					ui_qv_top(vi->view));
644 			return;
645 		}
646 
647 		/* Proceed and draw output of the previewer instead of returning, even
648 		 * though it's graphical.  This way it's possible to use an external
649 		 * previewer that handles both textual and graphical previews. */
650 	}
651 
652 	esc_state_init(&state, &cfg.cs.color[WIN_COLOR], COLORS);
653 
654 	ui_view_erase(vi->view, 1);
655 	ui_drop_attr(vi->view->win);
656 
657 	for(vl = 0, l = vi->line; l < max_l && vl < height; ++l)
658 	{
659 		int offset = 0;
660 		int processed = 0;
661 		char *const line = vi->lines[l];
662 		char *p = searched ? esc_highlight_pattern(line, &vi->re) : line;
663 		do
664 		{
665 			int printed;
666 			const int vis = l != vi->line
667 			             || vl + processed >= vi->linev - vi->widths[vi->line][0];
668 			offset += esc_print_line(p + offset, vi->view->win, ui_qv_left(vi->view),
669 					ui_qv_top(vi->view) + vl, width, !vis, !vi->wrap, &state, &printed);
670 			vl += vis;
671 			++processed;
672 		}
673 		while(vi->wrap && p[offset] != '\0' && vl < height);
674 		if(searched)
675 		{
676 			free(p);
677 		}
678 	}
679 	refresh_view_win(vi->view);
680 
681 	checked_wmove(vi->view->win, ui_qv_top(vi->view), ui_qv_left(vi->view));
682 }
683 
684 int
modview_find(const char pattern[],int backward)685 modview_find(const char pattern[], int backward)
686 {
687 	int err;
688 
689 	if(pattern == NULL)
690 		return 0;
691 
692 	if(vi->last_search_backward != -1)
693 		regfree(&vi->re);
694 	vi->last_search_backward = -1;
695 	if((err = regcomp(&vi->re, pattern, get_regexp_cflags(pattern))) != 0)
696 	{
697 		ui_sb_errf("Invalid pattern: %s", get_regexp_error(err, &vi->re));
698 		regfree(&vi->re);
699 		draw();
700 		return 1;
701 	}
702 
703 	vi->last_search_backward = backward;
704 
705 	search(vi->search_repeat, backward);
706 
707 	return curr_stats.save_msg;
708 }
709 
710 static void
cmd_ctrl_l(key_info_t key_info,keys_info_t * keys_info)711 cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info)
712 {
713 	modview_redraw();
714 }
715 
716 static void
cmd_ctrl_wH(key_info_t key_info,keys_info_t * keys_info)717 cmd_ctrl_wH(key_info_t key_info, keys_info_t *keys_info)
718 {
719 	move_window(get_active_view(), 0, 1);
720 }
721 
722 static void
cmd_ctrl_wJ(key_info_t key_info,keys_info_t * keys_info)723 cmd_ctrl_wJ(key_info_t key_info, keys_info_t *keys_info)
724 {
725 	move_window(get_active_view(), 1, 0);
726 }
727 
728 static void
cmd_ctrl_wK(key_info_t key_info,keys_info_t * keys_info)729 cmd_ctrl_wK(key_info_t key_info, keys_info_t *keys_info)
730 {
731 	move_window(get_active_view(), 1, 1);
732 }
733 
734 static void
cmd_ctrl_wL(key_info_t key_info,keys_info_t * keys_info)735 cmd_ctrl_wL(key_info_t key_info, keys_info_t *keys_info)
736 {
737 	move_window(get_active_view(), 0, 0);
738 }
739 
740 /* Gets pointer to the currently active view from the view point of the
741  * view-mode.  Returns that pointer. */
742 static view_t *
get_active_view(void)743 get_active_view(void)
744 {
745 	return (curr_stats.preview.on ? other_view : curr_view);
746 }
747 
748 void
modview_panes_swapped(void)749 modview_panes_swapped(void)
750 {
751 	if(curr_stats.preview.explore != NULL)
752 	{
753 		curr_stats.preview.explore->view = curr_view;
754 	}
755 
756 	if(lwin.vi != NULL)
757 	{
758 		lwin.vi->view = &lwin;
759 	}
760 	if(rwin.vi != NULL)
761 	{
762 		rwin.vi->view = &rwin;
763 	}
764 }
765 
766 /* Go to bottom-right window. */
767 static void
cmd_ctrl_wb(key_info_t key_info,keys_info_t * keys_info)768 cmd_ctrl_wb(key_info_t key_info, keys_info_t *keys_info)
769 {
770 	if(is_top_or_left())
771 	{
772 		cmd_ctrl_ww(key_info, keys_info);
773 	}
774 }
775 
776 static void
cmd_ctrl_wh(key_info_t key_info,keys_info_t * keys_info)777 cmd_ctrl_wh(key_info_t key_info, keys_info_t *keys_info)
778 {
779 	if(curr_stats.split == VSPLIT && is_right_or_bottom())
780 	{
781 		cmd_ctrl_ww(key_info, keys_info);
782 	}
783 }
784 
785 static void
cmd_ctrl_wj(key_info_t key_info,keys_info_t * keys_info)786 cmd_ctrl_wj(key_info_t key_info, keys_info_t *keys_info)
787 {
788 	if(curr_stats.split == HSPLIT && is_top_or_left())
789 	{
790 		cmd_ctrl_ww(key_info, keys_info);
791 	}
792 }
793 
794 static void
cmd_ctrl_wk(key_info_t key_info,keys_info_t * keys_info)795 cmd_ctrl_wk(key_info_t key_info, keys_info_t *keys_info)
796 {
797 	if(curr_stats.split == HSPLIT && is_right_or_bottom())
798 	{
799 		cmd_ctrl_ww(key_info, keys_info);
800 	}
801 }
802 
803 static void
cmd_ctrl_wl(key_info_t key_info,keys_info_t * keys_info)804 cmd_ctrl_wl(key_info_t key_info, keys_info_t *keys_info)
805 {
806 	if(curr_stats.split == VSPLIT && is_top_or_left())
807 	{
808 		cmd_ctrl_ww(key_info, keys_info);
809 	}
810 }
811 
812 /* Leave only one (current) pane. */
813 static void
cmd_ctrl_wo(key_info_t key_info,keys_info_t * keys_info)814 cmd_ctrl_wo(key_info_t key_info, keys_info_t *keys_info)
815 {
816 	if(vi->view->explore_mode)
817 	{
818 		only();
819 	}
820 	else
821 	{
822 		display_error("Can't leave preview window alone on the screen.");
823 	}
824 }
825 
826 static void
cmd_ctrl_ws(key_info_t key_info,keys_info_t * keys_info)827 cmd_ctrl_ws(key_info_t key_info, keys_info_t *keys_info)
828 {
829 	split_view(HSPLIT);
830 	modview_redraw();
831 }
832 
833 static void
cmd_ctrl_wt(key_info_t key_info,keys_info_t * keys_info)834 cmd_ctrl_wt(key_info_t key_info, keys_info_t *keys_info)
835 {
836 	if(is_right_or_bottom())
837 	{
838 		cmd_ctrl_ww(key_info, keys_info);
839 	}
840 }
841 
842 /* Checks whether active window is right of bottom one.  Returns non-zero if it
843  * is, otherwise zero is returned. */
844 static int
is_right_or_bottom(void)845 is_right_or_bottom(void)
846 {
847 	return !is_top_or_left();
848 }
849 
850 /* Checks whether active window is top of left one.  Returns non-zero if it is,
851  * otherwise zero is returned. */
852 static int
is_top_or_left(void)853 is_top_or_left(void)
854 {
855 	const view_t *const top_or_left = curr_view->explore_mode ? &lwin : &rwin;
856 	return curr_view == top_or_left;
857 }
858 
859 static void
cmd_ctrl_wv(key_info_t key_info,keys_info_t * keys_info)860 cmd_ctrl_wv(key_info_t key_info, keys_info_t *keys_info)
861 {
862 	split_view(VSPLIT);
863 	modview_redraw();
864 }
865 
866 static void
cmd_ctrl_ww(key_info_t key_info,keys_info_t * keys_info)867 cmd_ctrl_ww(key_info_t key_info, keys_info_t *keys_info)
868 {
869 	vi->detached = 1;
870 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
871 	if(curr_view->explore_mode)
872 	{
873 		go_to_other_pane();
874 	}
875 
876 	ui_views_update_titles();
877 	if(curr_stats.preview.on)
878 	{
879 		qv_draw(curr_view);
880 	}
881 }
882 
883 /* Switches views. */
884 static void
cmd_ctrl_wx(key_info_t key_info,keys_info_t * keys_info)885 cmd_ctrl_wx(key_info_t key_info, keys_info_t *keys_info)
886 {
887 	vi->detached = 1;
888 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
889 	switch_panes();
890 	if(curr_stats.preview.on)
891 	{
892 		change_window();
893 	}
894 }
895 
896 /* Quits preview pane or view modes. */
897 static void
cmd_ctrl_wz(key_info_t key_info,keys_info_t * keys_info)898 cmd_ctrl_wz(key_info_t key_info, keys_info_t *keys_info)
899 {
900 	modview_leave();
901 	qv_hide();
902 }
903 
904 static void
cmd_meta_space(key_info_t key_info,keys_info_t * keys_info)905 cmd_meta_space(key_info_t key_info, keys_info_t *keys_info)
906 {
907 	check_and_set_from_default_win(&key_info);
908 	key_info.reg = 1;
909 	cmd_j(key_info, keys_info);
910 }
911 
912 static void
cmd_percent(key_info_t key_info,keys_info_t * keys_info)913 cmd_percent(key_info_t key_info, keys_info_t *keys_info)
914 {
915 	if(key_info.count == NO_COUNT_GIVEN)
916 		key_info.count = 0;
917 	if(key_info.count > 100)
918 		key_info.count = 100;
919 
920 	vi->line = (key_info.count*vi->nlinesv)/100;
921 	if(vi->line >= vi->nlines)
922 		vi->line = vi->nlines - 1;
923 	vi->linev = vi->widths[vi->line][0];
924 	draw();
925 }
926 
927 static void
cmd_tab(key_info_t key_info,keys_info_t * keys_info)928 cmd_tab(key_info_t key_info, keys_info_t *keys_info)
929 {
930 	if(!curr_view->explore_mode)
931 	{
932 		modview_leave();
933 		return;
934 	}
935 
936 	change_window();
937 	if(!curr_view->explore_mode)
938 	{
939 		vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
940 	}
941 	pick_vi(curr_view->explore_mode);
942 
943 	ui_views_update_titles();
944 }
945 
946 /* Updates value of the vi variable and points it to the correct element of the
947  * view_info array according to view mode. */
948 static void
pick_vi(int explore)949 pick_vi(int explore)
950 {
951 	modview_info_t **ptr = explore ? &curr_view->vi
952 	                               : &curr_stats.preview.explore;
953 
954 	if(*ptr == NULL)
955 	{
956 		*ptr = view_info_alloc();
957 	}
958 
959 	vi = *ptr;
960 }
961 
962 static void
cmd_slash(key_info_t key_info,keys_info_t * keys_info)963 cmd_slash(key_info_t key_info, keys_info_t *keys_info)
964 {
965 	vi->search_repeat = key_info.count;
966 	modcline_enter(CLS_VWFSEARCH, "", NULL);
967 }
968 
969 static void
cmd_qmark(key_info_t key_info,keys_info_t * keys_info)970 cmd_qmark(key_info_t key_info, keys_info_t *keys_info)
971 {
972 	vi->search_repeat = key_info.count;
973 	modcline_enter(CLS_VWBSEARCH, "", NULL);
974 }
975 
976 /* Toggles automatic forwarding of file. */
977 static void
cmd_F(key_info_t key_info,keys_info_t * keys_info)978 cmd_F(key_info_t key_info, keys_info_t *keys_info)
979 {
980 	vi->auto_forward = !vi->auto_forward;
981 	if(vi->auto_forward)
982 	{
983 		if(forward_if_changed(vi) || scroll_to_bottom(vi))
984 		{
985 			draw();
986 		}
987 	}
988 }
989 
990 /* Either scrolls to specific line number (when specified) or to the bottom of
991  * the view. */
992 static void
cmd_G(key_info_t key_info,keys_info_t * keys_info)993 cmd_G(key_info_t key_info, keys_info_t *keys_info)
994 {
995 	if(key_info.count != NO_COUNT_GIVEN)
996 	{
997 		cmd_g(key_info, keys_info);
998 		return;
999 	}
1000 
1001 	if(scroll_to_bottom(vi))
1002 	{
1003 		draw();
1004 	}
1005 }
1006 
1007 static void
cmd_N(key_info_t key_info,keys_info_t * keys_info)1008 cmd_N(key_info_t key_info, keys_info_t *keys_info)
1009 {
1010 	goto_search_result(key_info.count, 1);
1011 }
1012 
1013 /* Handles view data reloading key. */
1014 static void
cmd_R(key_info_t key_info,keys_info_t * keys_info)1015 cmd_R(key_info_t key_info, keys_info_t *keys_info)
1016 {
1017 	reload_view(vi, NOSILENT);
1018 }
1019 
1020 /* Loads list of strings and related data into modview_info_t structure from
1021  * specified file.  The action parameter is a title to be used for error
1022  * messages.  Returns non-zero on error, otherwise zero is returned. */
1023 static int
load_view_data(modview_info_t * vi,const char action[],const char file_to_view[],int silent)1024 load_view_data(modview_info_t *vi, const char action[],
1025 		const char file_to_view[], int silent)
1026 {
1027 	const int error = get_view_data(vi, file_to_view);
1028 
1029 	if(error != 0 && silent)
1030 	{
1031 		return 1;
1032 	}
1033 
1034 	switch(error)
1035 	{
1036 		case 0:
1037 			break;
1038 
1039 		case 2:
1040 			show_error_msg(action, "Can't open file for reading");
1041 			return 1;
1042 		case 3:
1043 			show_error_msg(action, "Can't get data from viewer");
1044 			return 1;
1045 		case 4:
1046 			show_error_msg(action, "Nothing to explore");
1047 			return 1;
1048 
1049 		default:
1050 			assert(0 && "Unhandled error code.");
1051 			return 1;
1052 	}
1053 
1054 	if(vi->nlines != 0)
1055 	{
1056 		vi->widths = reallocarray(NULL, vi->nlines, sizeof(*vi->widths));
1057 		if(vi->widths == NULL)
1058 		{
1059 			free_string_array(vi->lines, vi->nlines);
1060 			vi->lines = NULL;
1061 			vi->nlines = 0;
1062 			show_error_msg(action, "Not enough memory");
1063 			return 1;
1064 		}
1065 	}
1066 
1067 	return 0;
1068 }
1069 
1070 /* Reads data to be displayed handling error cases.  Returns zero on success, 2
1071  * on file reading error, 3 on issues with viewer or 4 on empty input. */
1072 static int
get_view_data(modview_info_t * vi,const char file_to_view[])1073 get_view_data(modview_info_t *vi, const char file_to_view[])
1074 {
1075 	FILE *fp;
1076 	const char *const viewer = qv_get_viewer(file_to_view);
1077 
1078 	if(vi->viewer == NULL && is_null_or_empty(viewer))
1079 	{
1080 		if(is_dir(file_to_view))
1081 		{
1082 			ui_cancellation_push_on();
1083 			fp = qv_view_dir(file_to_view);
1084 			ui_cancellation_pop();
1085 		}
1086 		else
1087 		{
1088 			/* Binary mode is important on Windows. */
1089 			fp = os_fopen(file_to_view, "rb");
1090 		}
1091 
1092 		if(fp == NULL)
1093 		{
1094 			return 2;
1095 		}
1096 
1097 		vi->lines = read_file_lines(fp, &vi->nlines);
1098 	}
1099 	else
1100 	{
1101 		const char *const v = (vi->viewer != NULL) ? vi->viewer : viewer;
1102 		const ViewerKind kind = ft_viewer_kind(v);
1103 		view_t *const curr = curr_view;
1104 		curr_view = curr_stats.preview.on ? curr_view
1105 		          : (vi->view != NULL) ? vi->view : curr_view;
1106 
1107 		const preview_area_t parea = {
1108 			.source = vi->view,
1109 			.view = vi->view,
1110 			.x = ui_qv_left(vi->view),
1111 			.y = ui_qv_top(vi->view),
1112 			.w = ui_qv_width(vi->view),
1113 			.h = ui_qv_height(vi->view),
1114 		};
1115 		curr_stats.preview_hint = &parea;
1116 
1117 		if(kind != VK_TEXTUAL)
1118 		{
1119 			/* Wait a bit to let terminal emulator do actual refresh (at least some
1120 			 * of them need this). */
1121 			usleep(50000);
1122 		}
1123 		if(vi->viewer == NULL)
1124 		{
1125 			fp = qv_execute_viewer(viewer);
1126 		}
1127 		else
1128 		{
1129 			/* Don't add implicit %c to a command with %e macro. */
1130 			char *const cmd = ma_expand(vi->viewer, NULL, NULL, 1);
1131 			fp = read_cmd_output(cmd, 0);
1132 			free(cmd);
1133 		}
1134 
1135 		curr_view = curr;
1136 		curr_stats.preview_hint = NULL;
1137 
1138 		if(fp == NULL)
1139 		{
1140 			return 3;
1141 		}
1142 
1143 		vi->kind = kind;
1144 
1145 		ui_cancellation_push_on();
1146 		vi->lines = read_stream_lines(fp, &vi->nlines, 0, NULL, NULL);
1147 		ui_cancellation_pop();
1148 	}
1149 
1150 	fclose(fp);
1151 
1152 	if(vi->kind != VK_TEXTUAL && vi->nlines == 0)
1153 	{
1154 		/* Exploring absent output gives error, add an empty line to allow empty
1155 		 * output for graphical previewers. */
1156 		vi->nlines = add_to_string_array(&vi->lines, vi->nlines, "");
1157 	}
1158 	if(vi->lines == NULL || vi->nlines == 0)
1159 	{
1160 		return 4;
1161 	}
1162 
1163 	return 0;
1164 }
1165 
1166 /* Replaces modview_info_t structure with another one preserving as much as
1167  * possible. */
1168 static void
replace_vi(modview_info_t * orig,modview_info_t * new)1169 replace_vi(modview_info_t *orig, modview_info_t *new)
1170 {
1171 	new->filename = orig->filename;
1172 	orig->filename = NULL;
1173 
1174 	new->viewer = orig->viewer;
1175 	orig->viewer = NULL;
1176 
1177 	if(orig->last_search_backward != -1)
1178 	{
1179 		new->last_search_backward = orig->last_search_backward;
1180 		new->re = orig->re;
1181 		orig->last_search_backward = -1;
1182 	}
1183 
1184 	new->win_size = orig->win_size;
1185 	new->half_win = orig->half_win;
1186 	new->line = orig->line;
1187 	new->linev = orig->linev;
1188 	new->view = orig->view;
1189 	new->auto_forward = orig->auto_forward;
1190 	filemon_assign(&new->file_mon, &orig->file_mon);
1191 
1192 	free_view_info(orig);
1193 	*orig = *new;
1194 }
1195 
1196 static void
cmd_b(key_info_t key_info,keys_info_t * keys_info)1197 cmd_b(key_info_t key_info, keys_info_t *keys_info)
1198 {
1199 	check_and_set_from_default_win(&key_info);
1200 	cmd_k(key_info, keys_info);
1201 }
1202 
1203 static void
cmd_d(key_info_t key_info,keys_info_t * keys_info)1204 cmd_d(key_info_t key_info, keys_info_t *keys_info)
1205 {
1206 	update_with_half_win(&key_info);
1207 	cmd_j(key_info, keys_info);
1208 }
1209 
1210 static void
cmd_f(key_info_t key_info,keys_info_t * keys_info)1211 cmd_f(key_info_t key_info, keys_info_t *keys_info)
1212 {
1213 	check_and_set_from_default_win(&key_info);
1214 	cmd_j(key_info, keys_info);
1215 }
1216 
1217 /* Sets key count from scroll window size when count is not specified. */
1218 static void
check_and_set_from_default_win(key_info_t * key_info)1219 check_and_set_from_default_win(key_info_t *key_info)
1220 {
1221 	if(key_info->count == NO_COUNT_GIVEN)
1222 	{
1223 		set_from_default_win(key_info);
1224 	}
1225 }
1226 
1227 static void
cmd_g(key_info_t key_info,keys_info_t * keys_info)1228 cmd_g(key_info_t key_info, keys_info_t *keys_info)
1229 {
1230 	if(key_info.count == NO_COUNT_GIVEN)
1231 		key_info.count = 1;
1232 
1233 	key_info.count = MIN(vi->nlinesv - ui_qv_height(vi->view), key_info.count);
1234 	key_info.count = MAX(1, key_info.count);
1235 
1236 	if(vi->linev == vi->widths[key_info.count - 1][0])
1237 		return;
1238 	vi->line = key_info.count - 1;
1239 	vi->linev = vi->widths[vi->line][0];
1240 	draw();
1241 }
1242 
1243 static void
cmd_j(key_info_t key_info,keys_info_t * keys_info)1244 cmd_j(key_info_t key_info, keys_info_t *keys_info)
1245 {
1246 	if(key_info.reg == NO_REG_GIVEN)
1247 	{
1248 		if((vi->linev + 1) + ui_qv_height(vi->view) > vi->nlinesv)
1249 			return;
1250 	}
1251 	else
1252 	{
1253 		if(vi->linev + 1 > vi->nlinesv)
1254 			return;
1255 	}
1256 
1257 	if(key_info.count == NO_COUNT_GIVEN)
1258 		key_info.count = 1;
1259 	if(key_info.reg == NO_REG_GIVEN)
1260 		key_info.count = MIN(key_info.count,
1261 				vi->nlinesv - ui_qv_height(vi->view) - vi->linev);
1262 	else
1263 		key_info.count = MIN(key_info.count, vi->nlinesv - vi->linev - 1);
1264 
1265 	while(key_info.count-- > 0)
1266 	{
1267 		const int height = MAX(DIV_ROUND_UP(vi->widths[vi->line][1], vi->width), 1);
1268 		if(vi->linev + 1 >= vi->widths[vi->line][0] + height)
1269 			++vi->line;
1270 
1271 		++vi->linev;
1272 	}
1273 
1274 	draw();
1275 }
1276 
1277 static void
cmd_k(key_info_t key_info,keys_info_t * keys_info)1278 cmd_k(key_info_t key_info, keys_info_t *keys_info)
1279 {
1280 	if(vi->linev == 0)
1281 		return;
1282 
1283 	if(key_info.count == NO_COUNT_GIVEN)
1284 		key_info.count = 1;
1285 	key_info.count = MIN(key_info.count, vi->linev);
1286 
1287 	while(key_info.count-- > 0)
1288 	{
1289 		if(vi->linev - 1 < vi->widths[vi->line][0])
1290 			--vi->line;
1291 
1292 		--vi->linev;
1293 	}
1294 
1295 	draw();
1296 }
1297 
1298 static void
cmd_n(key_info_t key_info,keys_info_t * keys_info)1299 cmd_n(key_info_t key_info, keys_info_t *keys_info)
1300 {
1301 	goto_search_result(key_info.count, 0);
1302 }
1303 
1304 /* Goes to the next/previous match of the last search. */
1305 static void
goto_search_result(int repeat_count,int inverse_direction)1306 goto_search_result(int repeat_count, int inverse_direction)
1307 {
1308 	const int backward = inverse_direction ?
1309 		!vi->last_search_backward : vi->last_search_backward;
1310 	search(repeat_count, backward);
1311 }
1312 
1313 /* Performs search and navigation to the first match. */
1314 static void
search(int repeat_count,int backward)1315 search(int repeat_count, int backward)
1316 {
1317 	if(vi->last_search_backward == -1)
1318 	{
1319 		return;
1320 	}
1321 
1322 	if(repeat_count == NO_COUNT_GIVEN)
1323 	{
1324 		repeat_count = 1;
1325 	}
1326 
1327 	while(repeat_count-- > 0 && curr_stats.save_msg == 0)
1328 	{
1329 		if(backward)
1330 		{
1331 			find_previous(1);
1332 		}
1333 		else
1334 		{
1335 			find_next();
1336 		}
1337 	}
1338 }
1339 
1340 static void
find_previous(int vline_offset)1341 find_previous(int vline_offset)
1342 {
1343 	int i;
1344 	int offset = 0;
1345 	char buf[ui_qv_width(vi->view)*4];
1346 	int vl, l;
1347 
1348 	vl = vi->linev - vline_offset;
1349 	l = vi->line;
1350 
1351 	if(l > 0 && vl < vi->widths[l][0])
1352 		l--;
1353 
1354 	for(i = 0; i <= vl - vi->widths[l][0]; i++)
1355 		offset = get_part(vi->lines[l], offset, ui_qv_width(vi->view), buf);
1356 
1357 	/* Don't stop until we go above first virtual line of the first line. */
1358 	while(l >= 0 && vl >= 0)
1359 	{
1360 		if(regexec(&vi->re, buf, 0, NULL, 0) == 0)
1361 		{
1362 			vi->linev = vl;
1363 			vi->line = l;
1364 			break;
1365 		}
1366 		if(l > 0 && vl - 1 < vi->widths[l][0])
1367 		{
1368 			l--;
1369 			offset = 0;
1370 			for(i = 0; i <= vl - 1 - vi->widths[l][0]; i++)
1371 				offset = get_part(vi->lines[l], offset, ui_qv_width(vi->view), buf);
1372 		}
1373 		else
1374 			offset = get_part(vi->lines[l], offset, ui_qv_width(vi->view), buf);
1375 		vl--;
1376 	}
1377 	draw();
1378 	if(vi->line != l)
1379 	{
1380 		display_error("Pattern not found");
1381 	}
1382 }
1383 
1384 static void
find_next(void)1385 find_next(void)
1386 {
1387 	int i;
1388 	int offset = 0;
1389 	char buf[ui_qv_width(vi->view)*4];
1390 	int vl, l;
1391 
1392 	vl = vi->linev + 1;
1393 	l = vi->line;
1394 
1395 	if(l < vi->nlines - 1 && vl == vi->widths[l + 1][0])
1396 		l++;
1397 
1398 	for(i = 0; i <= vl - vi->widths[l][0]; i++)
1399 		offset = get_part(vi->lines[l], offset, ui_qv_width(vi->view), buf);
1400 
1401 	while(l < vi->nlines)
1402 	{
1403 		if(regexec(&vi->re, buf, 0, NULL, 0) == 0)
1404 		{
1405 			vi->linev = vl;
1406 			vi->line = l;
1407 			break;
1408 		}
1409 		if(l < vi->nlines && (l == vi->nlines - 1 ||
1410 				vl + 1 >= vi->widths[l + 1][0]))
1411 		{
1412 			if(l == vi->nlines - 1)
1413 				break;
1414 			l++;
1415 			offset = 0;
1416 		}
1417 		offset = get_part(vi->lines[l], offset, ui_qv_width(vi->view), buf);
1418 		vl++;
1419 	}
1420 	draw();
1421 	if(vi->line != l)
1422 	{
1423 		display_error("Pattern not found");
1424 	}
1425 }
1426 
1427 /* Extracts part of the line replacing all occurrences of horizontal tabulation
1428  * character with appropriate number of spaces.  The offset specifies beginning
1429  * of the part in the line.  The max_len parameter designates the maximum number
1430  * of screen characters to put into the part.  Returns number of processed items
1431  * of the line. */
1432 static int
get_part(const char line[],int offset,size_t max_len,char part[])1433 get_part(const char line[], int offset, size_t max_len, char part[])
1434 {
1435 	char *const no_esc = esc_remove(line);
1436 	const char *const begin = no_esc + offset;
1437 	const char *const end = expand_tabulation(begin, max_len, cfg.tab_stop, part);
1438 	const ptrdiff_t processed_chars = end - no_esc;
1439 	free(no_esc);
1440 	return processed_chars;
1441 }
1442 
1443 /* Displays the error message in the status bar. */
1444 static void
display_error(const char error_msg[])1445 display_error(const char error_msg[])
1446 {
1447 	ui_sb_err(error_msg);
1448 	curr_stats.save_msg = 1;
1449 }
1450 
1451 static void
cmd_q(key_info_t key_info,keys_info_t * keys_info)1452 cmd_q(key_info_t key_info, keys_info_t *keys_info)
1453 {
1454 	modview_leave();
1455 }
1456 
1457 static void
cmd_u(key_info_t key_info,keys_info_t * keys_info)1458 cmd_u(key_info_t key_info, keys_info_t *keys_info)
1459 {
1460 	update_with_half_win(&key_info);
1461 	cmd_k(key_info, keys_info);
1462 }
1463 
1464 /* Sets key count from half window size when count is not specified, otherwise
1465  * specified count is stored as new size of the half window size. */
1466 static void
update_with_half_win(key_info_t * key_info)1467 update_with_half_win(key_info_t *key_info)
1468 {
1469 	if(key_info->count == NO_COUNT_GIVEN)
1470 	{
1471 		key_info->count = (vi->half_win > 0)
1472 			? vi->half_win
1473 			: ui_qv_height(vi->view)/2;
1474 	}
1475 	else
1476 	{
1477 		vi->half_win = key_info->count;
1478 	}
1479 }
1480 
1481 /* Invokes an editor to edit the current file being viewed.  The command for
1482  * editing is taken from the 'vicmd'/'vixcmd' option value and extended with
1483  * middle line number prepended by a plus sign and name of the current file. */
1484 static void
cmd_v(key_info_t key_info,keys_info_t * keys_info)1485 cmd_v(key_info_t key_info, keys_info_t *keys_info)
1486 {
1487 	char path[PATH_MAX + 1];
1488 	get_current_full_path(curr_view, sizeof(path), path);
1489 	(void)vim_view_file(path, vi->line + ui_qv_height(vi->view)/2, -1, 1);
1490 	/* In some cases two redraw operations are needed, otherwise TUI is not fully
1491 	 * redrawn. */
1492 	update_screen(UT_REDRAW);
1493 	stats_redraw_later();
1494 }
1495 
1496 static void
cmd_w(key_info_t key_info,keys_info_t * keys_info)1497 cmd_w(key_info_t key_info, keys_info_t *keys_info)
1498 {
1499 	update_with_win(&key_info);
1500 	cmd_k(key_info, keys_info);
1501 }
1502 
1503 static void
cmd_z(key_info_t key_info,keys_info_t * keys_info)1504 cmd_z(key_info_t key_info, keys_info_t *keys_info)
1505 {
1506 	update_with_win(&key_info);
1507 	cmd_j(key_info, keys_info);
1508 }
1509 
1510 /* Sets key count from scroll window size when count is not specified, otherwise
1511  * specified count is stored as new size of the scroll window. */
1512 static void
update_with_win(key_info_t * key_info)1513 update_with_win(key_info_t *key_info)
1514 {
1515 	if(key_info->count == NO_COUNT_GIVEN)
1516 	{
1517 		set_from_default_win(key_info);
1518 	}
1519 	else
1520 	{
1521 		vi->win_size = key_info->count;
1522 	}
1523 }
1524 
1525 /* Sets key count from scroll window size. */
1526 static void
set_from_default_win(key_info_t * key_info)1527 set_from_default_win(key_info_t *key_info)
1528 {
1529 	key_info->count = (vi->win_size > 0)
1530 		? vi->win_size
1531 		: (ui_qv_height(vi->view) - 1);
1532 }
1533 
1534 int
modview_detached_draw(void)1535 modview_detached_draw(void)
1536 {
1537 	pick_vi(0);
1538 
1539 	if(vi == NULL || !vi->detached)
1540 	{
1541 		return 0;
1542 	}
1543 
1544 	if(is_trying_the_same_file())
1545 	{
1546 		redraw();
1547 		return 1;
1548 	}
1549 
1550 	reset_view_info(vi);
1551 	return 0;
1552 }
1553 
1554 static int
is_trying_the_same_file(void)1555 is_trying_the_same_file(void)
1556 {
1557 	char full_path[PATH_MAX + 1];
1558 
1559 	if(get_file_to_explore(curr_view, full_path, sizeof(full_path)) != 0)
1560 	{
1561 		return 0;
1562 	}
1563 
1564 	if(stroscmp(vi->filename, full_path) != 0)
1565 	{
1566 		return 0;
1567 	}
1568 
1569 	return 1;
1570 }
1571 
1572 /* Gets full path to the file that will be explored (the current file of the
1573  * view).  Returns non-zero if file cannot be explored. */
1574 static int
get_file_to_explore(const view_t * view,char buf[],size_t buf_len)1575 get_file_to_explore(const view_t *view, char buf[], size_t buf_len)
1576 {
1577 	const dir_entry_t *const curr = get_current_entry(view);
1578 	if(fentry_is_fake(curr))
1579 	{
1580 		return 1;
1581 	}
1582 
1583 	qv_get_path_to_explore(curr, buf, buf_len);
1584 
1585 	switch(curr->type)
1586 	{
1587 		case FT_CHAR_DEV:
1588 		case FT_BLOCK_DEV:
1589 		case FT_FIFO:
1590 #ifndef _WIN32
1591 		case FT_SOCK:
1592 #endif
1593 			return 1;
1594 		case FT_LINK:
1595 			if(get_link_target_abs(buf, curr->origin, buf, buf_len) != 0)
1596 			{
1597 				return 1;
1598 			}
1599 			return (os_access(buf, R_OK) != 0);
1600 
1601 		default:
1602 			return 0;
1603 	}
1604 }
1605 
1606 void
modview_check_for_updates(void)1607 modview_check_for_updates(void)
1608 {
1609 	int need_redraw = 0;
1610 
1611 	need_redraw += forward_if_changed(curr_stats.preview.explore);
1612 	need_redraw += forward_if_changed(lwin.vi);
1613 	need_redraw += forward_if_changed(rwin.vi);
1614 
1615 	if(need_redraw)
1616 	{
1617 		stats_redraw_later();
1618 	}
1619 }
1620 
1621 /* Forwards the view if underlying file changed.  Returns non-zero if reload
1622  * occurred, otherwise zero is returned. */
1623 static int
forward_if_changed(modview_info_t * vi)1624 forward_if_changed(modview_info_t *vi)
1625 {
1626 	filemon_t mon;
1627 
1628 	if(vi == NULL || !vi->auto_forward)
1629 	{
1630 		return 0;
1631 	}
1632 
1633 	if(filemon_from_file(vi->filename, FMT_MODIFIED, &mon) != 0)
1634 	{
1635 		return 0;
1636 	}
1637 
1638 	if(filemon_equal(&mon, &vi->file_mon))
1639 	{
1640 		return 0;
1641 	}
1642 
1643 	filemon_assign(&vi->file_mon, &mon);
1644 	reload_view(vi, SILENT);
1645 	return scroll_to_bottom(vi);
1646 }
1647 
1648 /* Scrolls view to the bottom if there is any room for that.  Returns non-zero
1649  * if position was changed, otherwise zero is returned. */
1650 static int
scroll_to_bottom(modview_info_t * vi)1651 scroll_to_bottom(modview_info_t *vi)
1652 {
1653 	if(vi->linev + 1 + ui_qv_height(vi->view) > vi->nlinesv)
1654 	{
1655 		return 0;
1656 	}
1657 
1658 	vi->linev = vi->nlinesv - ui_qv_height(vi->view);
1659 	for(vi->line = 0; vi->line < vi->nlines - 1; ++vi->line)
1660 	{
1661 		if(vi->linev < vi->widths[vi->line + 1][0])
1662 		{
1663 			break;
1664 		}
1665 	}
1666 
1667 	return 1;
1668 }
1669 
1670 /* Reloads contents of the specified view by rerunning corresponding viewer or
1671  * just rereading a file. */
1672 static void
reload_view(modview_info_t * vi,int silent)1673 reload_view(modview_info_t *vi, int silent)
1674 {
1675 	modview_info_t new_vi;
1676 
1677 	init_view_info(&new_vi);
1678 	/* These fields are used in get_view_data(). */
1679 	new_vi.view = vi->view;
1680 	new_vi.viewer = vi->viewer;
1681 
1682 	if(load_view_data(&new_vi, "File exploring reload", vi->filename, silent)
1683 			== 0)
1684 	{
1685 		replace_vi(vi, &new_vi);
1686 		modview_redraw();
1687 	}
1688 }
1689 
1690 void
modview_hide_graphics(void)1691 modview_hide_graphics(void)
1692 {
1693 	if(lwin.vi != NULL && lwin.vi->kind != VK_TEXTUAL)
1694 	{
1695 		cleanup(lwin.vi);
1696 	}
1697 	if(rwin.vi != NULL && rwin.vi->kind != VK_TEXTUAL)
1698 	{
1699 		cleanup(rwin.vi);
1700 	}
1701 }
1702 
1703 /* Cleans view area, possibly using an external application. */
1704 static void
cleanup(modview_info_t * vi)1705 cleanup(modview_info_t *vi)
1706 {
1707 	const char *cmd = qv_get_viewer(vi->filename);
1708 	cmd = (cmd != NULL ? ma_get_clear_cmd(cmd) : NULL);
1709 	qv_cleanup(vi->view, cmd);
1710 }
1711 
1712 const char *
modview_detached_get_viewer(void)1713 modview_detached_get_viewer(void)
1714 {
1715 	return (vi == NULL ? NULL : vi->viewer);
1716 }
1717 
1718 /* Allocates and initializes view mode information.  Returns pointer to it. */
1719 static modview_info_t *
view_info_alloc(void)1720 view_info_alloc(void)
1721 {
1722 	modview_info_t *const vi = malloc(sizeof(*vi));
1723 	init_view_info(vi);
1724 	return vi;
1725 }
1726 
1727 void
modview_info_free(modview_info_t * info)1728 modview_info_free(modview_info_t *info)
1729 {
1730 	if(info != NULL)
1731 	{
1732 		free_view_info(info);
1733 		free(info);
1734 		if(info == vi)
1735 		{
1736 			vi = NULL;
1737 		}
1738 	}
1739 }
1740 
1741 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1742 /* vim: set cinoptions+=t0 filetype=c : */
1743