1 /* vifm
2 * Copyright (C) 2015 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 "more.h"
20
21 #include <curses.h>
22
23 #include <assert.h> /* assert() */
24 #include <limits.h> /* INT_MAX */
25 #include <stddef.h> /* NULL size_t */
26 #include <string.h> /* strdup() */
27
28 #include "../cfg/config.h"
29 #include "../compat/curses.h"
30 #include "../compat/reallocarray.h"
31 #include "../engine/keys.h"
32 #include "../engine/mode.h"
33 #include "../menus/menus.h"
34 #include "../ui/ui.h"
35 #include "../utils/macros.h"
36 #include "../utils/str.h"
37 #include "../utils/utf8.h"
38 #include "cmdline.h"
39 #include "modes.h"
40 #include "wk.h"
41
42 /* Provide more readable definitions of key codes. */
43
44 static void calc_vlines_wrapped(void);
45 static void leave_more_mode(void);
46 static const char * get_text_beginning(void);
47 static void draw_all(const char text[]);
48 static void cmd_leave(key_info_t key_info, keys_info_t *keys_info);
49 static void cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info);
50 static void cmd_colon(key_info_t key_info, keys_info_t *keys_info);
51 static void cmd_bottom(key_info_t key_info, keys_info_t *keys_info);
52 static void cmd_top(key_info_t key_info, keys_info_t *keys_info);
53 static void cmd_down_line(key_info_t key_info, keys_info_t *keys_info);
54 static void cmd_up_line(key_info_t key_info, keys_info_t *keys_info);
55 static void cmd_down_screen(key_info_t key_info, keys_info_t *keys_info);
56 static void cmd_up_screen(key_info_t key_info, keys_info_t *keys_info);
57 static void cmd_down_page(key_info_t key_info, keys_info_t *keys_info);
58 static void cmd_up_page(key_info_t key_info, keys_info_t *keys_info);
59 static void goto_vline(int line);
60 static void goto_vline_below(int by);
61 static void goto_vline_above(int by);
62
63 /* Whether UI was redrawn while this mode was active. */
64 static int was_redraw;
65
66 /* Text displayed by the mode. */
67 static char *text;
68 /* (first virtual line, screen width, offset in text) triples per real line. */
69 static int (*data)[3];
70
71 /* Number of virtual lines. */
72 static int nvlines;
73
74 /* Current real line number. */
75 static int curr_line;
76 /* Current virtual line number. */
77 static int curr_vline;
78
79 /* Width of the area text is printed on. */
80 static int viewport_width;
81 /* Height of the area text is printed on. */
82 static int viewport_height;
83
84 /* List of builtin keys. */
85 static keys_add_info_t builtin_keys[] = {
86 {WK_C_c, {{&cmd_leave}, .descr = "leave more mode"}},
87 {WK_C_j, {{&cmd_down_line}, .descr = "scroll one line down"}},
88 {WK_C_l, {{&cmd_ctrl_l}, .descr = "redraw"}},
89
90 {WK_COLON, {{&cmd_colon}, .descr = "switch to cmdline mode"}},
91 {WK_ESC, {{&cmd_leave}, .descr = "leave more mode"}},
92 {WK_CR, {{&cmd_leave}, .descr = "leave more mode"}},
93 {WK_SPACE, {{&cmd_down_screen}, .descr = "scroll one screen down"}},
94
95 {WK_G, {{&cmd_bottom}, .descr = "scroll to the end"}},
96 {WK_b, {{&cmd_up_screen}, .descr = "scroll one screen up"}},
97 {WK_d, {{&cmd_down_page}, .descr = "scroll page down"}},
98 {WK_f, {{&cmd_down_screen}, .descr = "scroll one screen down"}},
99 {WK_g, {{&cmd_top}, .descr = "scroll to the beginning"}},
100 {WK_j, {{&cmd_down_line}, .descr = "scroll one line down"}},
101 {WK_k, {{&cmd_up_line}, .descr = "scroll one line up"}},
102 {WK_q, {{&cmd_leave}, .descr = "leave more mode"}},
103 {WK_u, {{&cmd_up_page}, .descr = "scroll page up"}},
104
105 #ifdef ENABLE_EXTENDED_KEYS
106 {{K(KEY_BACKSPACE)}, {{&cmd_up_line}, .descr = "scroll one line up"}},
107 {{K(KEY_DOWN)}, {{&cmd_down_line}, .descr = "scroll one line down"}},
108 {{K(KEY_UP)}, {{&cmd_up_line}, .descr = "scroll one line up"}},
109 {{K(KEY_HOME)}, {{&cmd_top}, .descr = "scroll to the beginning"}},
110 {{K(KEY_END)}, {{&cmd_bottom}, .descr = "scroll to the end"}},
111 {{K(KEY_NPAGE)}, {{&cmd_down_screen}, .descr = "scroll one screen down"}},
112 {{K(KEY_PPAGE)}, {{&cmd_up_screen}, .descr = "scroll one screen up"}},
113 #endif /* ENABLE_EXTENDED_KEYS */
114 };
115
116 void
modmore_init(void)117 modmore_init(void)
118 {
119 int ret_code;
120
121 ret_code = vle_keys_add(builtin_keys, ARRAY_LEN(builtin_keys), MORE_MODE);
122 assert(ret_code == 0 && "Failed to initialize more mode keys.");
123
124 (void)ret_code;
125 }
126
127 void
modmore_enter(const char txt[])128 modmore_enter(const char txt[])
129 {
130 text = strdup(txt);
131 curr_line = 0;
132 curr_vline = 0;
133
134 vle_mode_set(MORE_MODE, VMT_PRIMARY);
135
136 modmore_redraw();
137 was_redraw = 0;
138 }
139
140 void
modmore_abort(void)141 modmore_abort(void)
142 {
143 leave_more_mode();
144 }
145
146 /* Recalculates virtual lines of a view with line wrapping. */
147 static void
calc_vlines_wrapped(void)148 calc_vlines_wrapped(void)
149 {
150 const char *p;
151 char *q;
152
153 int i;
154 const int nlines = count_lines(text, INT_MAX);
155
156 data = reallocarray(NULL, nlines, sizeof(*data));
157
158 nvlines = 0;
159
160 p = text;
161 q = text - 1;
162
163 for(i = 0; i < nlines; ++i)
164 {
165 char saved_char;
166 q = until_first(q + 1, '\n');
167 saved_char = *q;
168 *q = '\0';
169
170 data[i][0] = nvlines++;
171 data[i][1] = utf8_strsw_with_tabs(p, cfg.tab_stop);
172 data[i][2] = p - text;
173 nvlines += data[i][1]/viewport_width;
174
175 *q = saved_char;
176 p = q + 1;
177 }
178 }
179
180 /* Quits the mode restoring previously active one. */
181 static void
leave_more_mode(void)182 leave_more_mode(void)
183 {
184 update_string(&text, NULL);
185 free(data);
186 data = NULL;
187
188 vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
189
190 if(was_redraw)
191 {
192 update_screen(UT_FULL);
193 }
194 else
195 {
196 update_all_windows();
197 }
198 }
199
200 void
modmore_redraw(void)201 modmore_redraw(void)
202 {
203 if(resize_for_menu_like() != 0)
204 {
205 return;
206 }
207 wresize(status_bar, 1, getmaxx(stdscr));
208
209 viewport_width = getmaxx(menu_win);
210 viewport_height = getmaxy(menu_win);
211 calc_vlines_wrapped();
212 goto_vline(curr_vline);
213
214 draw_all(get_text_beginning());
215 checked_wmove(menu_win, 0, 0);
216
217 was_redraw = 1;
218 }
219
220 /* Retrieves beginning of the text that should be displayed. Returns the
221 * beginning. */
222 static const char *
get_text_beginning(void)223 get_text_beginning(void)
224 {
225 int skipped = 0;
226 const char *text_piece = text + data[curr_line][2];
227 /* Skip invisible virtual lines (those that are above current one). */
228 while(skipped < curr_vline - data[curr_line][0])
229 {
230 text_piece += utf8_strsnlen(text_piece, viewport_width);
231 ++skipped;
232 }
233 return text_piece;
234 }
235
236 /* Draws all components of the mode onto the screen. */
237 static void
draw_all(const char text[])238 draw_all(const char text[])
239 {
240 /* Setup correct attributes for the windows. */
241 ui_set_attr(menu_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
242 ui_set_attr(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
243 cfg.cs.pair[CMD_LINE_COLOR]);
244
245 /* Clean up everything. */
246 werase(menu_win);
247 werase(status_bar);
248
249 /* Draw the text. */
250 checked_wmove(menu_win, 0, 0);
251 wprint(menu_win, text);
252
253 /* Draw status line. */
254 checked_wmove(status_bar, 0, 0);
255 mvwprintw(status_bar, 0, 0, "-- More -- %d-%d/%d", curr_vline + 1,
256 MIN(nvlines, curr_vline + viewport_height), nvlines);
257
258 /* Inform curses of the changes. */
259 wnoutrefresh(menu_win);
260 wnoutrefresh(status_bar);
261
262 /* Apply all changes. */
263 doupdate();
264
265 was_redraw = 1;
266 }
267
268 /* Leaves the mode. */
269 static void
cmd_leave(key_info_t key_info,keys_info_t * keys_info)270 cmd_leave(key_info_t key_info, keys_info_t *keys_info)
271 {
272 leave_more_mode();
273 }
274
275 /* Redraws the mode. */
276 static void
cmd_ctrl_l(key_info_t key_info,keys_info_t * keys_info)277 cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info)
278 {
279 modmore_redraw();
280 }
281
282 /* Switches to command-line mode. */
283 static void
cmd_colon(key_info_t key_info,keys_info_t * keys_info)284 cmd_colon(key_info_t key_info, keys_info_t *keys_info)
285 {
286 leave_more_mode();
287 modcline_enter(CLS_COMMAND, "", NULL);
288 }
289
290 /* Navigate to the bottom. */
291 static void
cmd_bottom(key_info_t key_info,keys_info_t * keys_info)292 cmd_bottom(key_info_t key_info, keys_info_t *keys_info)
293 {
294 goto_vline(INT_MAX);
295 }
296
297 /* Navigate to the top. */
298 static void
cmd_top(key_info_t key_info,keys_info_t * keys_info)299 cmd_top(key_info_t key_info, keys_info_t *keys_info)
300 {
301 goto_vline(0);
302 }
303
304 /* Go one line below. */
305 static void
cmd_down_line(key_info_t key_info,keys_info_t * keys_info)306 cmd_down_line(key_info_t key_info, keys_info_t *keys_info)
307 {
308 goto_vline(curr_vline + 1);
309 }
310
311 /* Go one line above. */
312 static void
cmd_up_line(key_info_t key_info,keys_info_t * keys_info)313 cmd_up_line(key_info_t key_info, keys_info_t *keys_info)
314 {
315 goto_vline(curr_vline - 1);
316 }
317
318 /* Go one screen below. */
319 static void
cmd_down_screen(key_info_t key_info,keys_info_t * keys_info)320 cmd_down_screen(key_info_t key_info, keys_info_t *keys_info)
321 {
322 goto_vline(curr_vline + viewport_height);
323 }
324
325 /* Go one screen above. */
326 static void
cmd_up_screen(key_info_t key_info,keys_info_t * keys_info)327 cmd_up_screen(key_info_t key_info, keys_info_t *keys_info)
328 {
329 goto_vline(curr_vline - viewport_height);
330 }
331
332 /* Go one page (half of the screen) below. */
333 static void
cmd_down_page(key_info_t key_info,keys_info_t * keys_info)334 cmd_down_page(key_info_t key_info, keys_info_t *keys_info)
335 {
336 goto_vline(curr_vline + viewport_height/2);
337 }
338
339 /* Go one page (half of the screen) above. */
340 static void
cmd_up_page(key_info_t key_info,keys_info_t * keys_info)341 cmd_up_page(key_info_t key_info, keys_info_t *keys_info)
342 {
343 goto_vline(curr_vline - viewport_height/2);
344 }
345
346 /* Navigates to the specified virtual line taking care of values that are out of
347 * range. */
348 static void
goto_vline(int line)349 goto_vline(int line)
350 {
351 const int max_vline = nvlines - viewport_height;
352
353 if(line > max_vline)
354 {
355 line = max_vline;
356 }
357 if(line < 0)
358 {
359 line = 0;
360 }
361
362 if(curr_vline == line)
363 {
364 return;
365 }
366
367 if(line > curr_vline)
368 {
369 goto_vline_below(line - curr_vline);
370 }
371 else
372 {
373 goto_vline_above(curr_vline - line);
374 }
375
376 modmore_redraw();
377 }
378
379 /* Navigates by virtual lines below. */
380 static void
goto_vline_below(int by)381 goto_vline_below(int by)
382 {
383 while(by-- > 0)
384 {
385 const int height = MAX(DIV_ROUND_UP(data[curr_line][1], viewport_width), 1);
386 if(curr_vline + 1 >= data[curr_line][0] + height)
387 {
388 ++curr_line;
389 }
390
391 ++curr_vline;
392 }
393 }
394
395 /* Navigates by virtual lines above. */
396 static void
goto_vline_above(int by)397 goto_vline_above(int by)
398 {
399 while(by-- > 0)
400 {
401 if(curr_vline - 1 < data[curr_line][0])
402 {
403 --curr_line;
404 }
405
406 --curr_vline;
407 }
408 }
409
410 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
411 /* vim: set cinoptions+=t0 filetype=c : */
412