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