1 /*
2 Internal file viewer for the Midnight Commander
3 Functions for handle cursor movement
4
5 Copyright (C) 1994-2021
6 Free Software Foundation, Inc.
7
8 Written by:
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
11 Jakub Jelinek, 1995
12 Joseph M. Hinkle, 1996
13 Norbert Warmuth, 1997
14 Pavel Machek, 1998
15 Roland Illig <roland.illig@gmx.de>, 2004, 2005
16 Slava Zanko <slavazanko@google.com>, 2009
17 Andrew Borodin <aborodin@vmail.ru>, 2009, 2013
18 Ilia Maslakov <il.smind@gmail.com>, 2009, 2010
19
20 This file is part of the Midnight Commander.
21
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
26
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <http://www.gnu.org/licenses/>.
34 */
35
36 /*
37 The following variables have to do with the current position and are
38 updated by the cursor movement functions.
39
40 In hex view and wrapped text view mode, dpy_start marks the offset of
41 the top-left corner on the screen, in non-wrapping text mode it is
42 the beginning of the current line. In hex mode, hex_cursor is the
43 offset of the cursor. In non-wrapping text mode, dpy_text_column is
44 the number of columns that are hidden on the left side on the screen.
45
46 In hex mode, dpy_start is updated by the view_fix_cursor_position()
47 function in order to keep the other functions simple. In
48 non-wrapping text mode dpy_start and dpy_text_column are normalized
49 such that dpy_text_column < view_get_datacolumns().
50 */
51
52 #include <config.h>
53
54 #include "lib/global.h"
55 #include "lib/tty/tty.h"
56 #include "internal.h"
57
58 /*** global variables ****************************************************************************/
59
60 /*** file scope macro definitions ****************************************************************/
61
62 /*** file scope type declarations ****************************************************************/
63
64 /*** file scope variables ************************************************************************/
65
66 /* --------------------------------------------------------------------------------------------- */
67 /*** file scope functions ************************************************************************/
68 /* --------------------------------------------------------------------------------------------- */
69
70 static void
mcview_scroll_to_cursor(WView * view)71 mcview_scroll_to_cursor (WView * view)
72 {
73 if (view->mode_flags.hex)
74 {
75 off_t bytes = view->bytes_per_line;
76 off_t cursor = view->hex_cursor;
77 off_t topleft = view->dpy_start;
78 off_t displaysize;
79
80 displaysize = view->data_area.height * bytes;
81 if (topleft + displaysize <= cursor)
82 topleft = mcview_offset_rounddown (cursor, bytes) - (displaysize - bytes);
83 if (cursor < topleft)
84 topleft = mcview_offset_rounddown (cursor, bytes);
85 view->dpy_start = topleft;
86 view->dpy_paragraph_skip_lines = 0;
87 view->dpy_wrap_dirty = TRUE;
88 }
89 }
90
91 /* --------------------------------------------------------------------------------------------- */
92
93 static void
mcview_movement_fixups(WView * view,gboolean reset_search)94 mcview_movement_fixups (WView * view, gboolean reset_search)
95 {
96 mcview_scroll_to_cursor (view);
97 if (reset_search)
98 {
99 view->search_start = view->mode_flags.hex ? view->hex_cursor : view->dpy_start;
100 view->search_end = view->search_start;
101 }
102 view->dirty++;
103 }
104
105 /* --------------------------------------------------------------------------------------------- */
106 /*** public functions ****************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
108
109 void
mcview_move_up(WView * view,off_t lines)110 mcview_move_up (WView * view, off_t lines)
111 {
112 if (view->mode_flags.hex)
113 {
114 off_t bytes = lines * view->bytes_per_line;
115
116 if (view->hex_cursor >= bytes)
117 {
118 view->hex_cursor -= bytes;
119 if (view->hex_cursor < view->dpy_start)
120 {
121 view->dpy_start = DOZ (view->dpy_start, bytes);
122 view->dpy_paragraph_skip_lines = 0;
123 view->dpy_wrap_dirty = TRUE;
124 }
125 }
126 else
127 {
128 view->hex_cursor %= view->bytes_per_line;
129 }
130 }
131 else
132 {
133 mcview_ascii_move_up (view, lines);
134 }
135 mcview_movement_fixups (view, TRUE);
136 }
137
138 /* --------------------------------------------------------------------------------------------- */
139
140 void
mcview_move_down(WView * view,off_t lines)141 mcview_move_down (WView * view, off_t lines)
142 {
143 off_t last_byte;
144
145 last_byte = mcview_get_filesize (view);
146
147 if (view->mode_flags.hex)
148 {
149 off_t i, limit;
150
151 limit = DOZ (last_byte, (off_t) view->bytes_per_line);
152
153 for (i = 0; i < lines && view->hex_cursor < limit; i++)
154 {
155 view->hex_cursor += view->bytes_per_line;
156 if (lines != 1)
157 {
158 view->dpy_start += view->bytes_per_line;
159 view->dpy_paragraph_skip_lines = 0;
160 view->dpy_wrap_dirty = TRUE;
161 }
162 }
163 }
164 else
165 {
166 mcview_ascii_move_down (view, lines);
167 }
168 mcview_movement_fixups (view, TRUE);
169 }
170
171 /* --------------------------------------------------------------------------------------------- */
172
173 void
mcview_move_left(WView * view,off_t columns)174 mcview_move_left (WView * view, off_t columns)
175 {
176 if (view->mode_flags.hex)
177 {
178 off_t old_cursor = view->hex_cursor;
179
180 g_assert (columns == 1);
181
182 if (view->hexview_in_text || !view->hexedit_lownibble)
183 {
184 if (view->hex_cursor > 0)
185 view->hex_cursor--;
186 }
187 if (!view->hexview_in_text)
188 if (old_cursor > 0 || view->hexedit_lownibble)
189 view->hexedit_lownibble = !view->hexedit_lownibble;
190 }
191 else if (!view->mode_flags.wrap)
192 view->dpy_text_column = DOZ (view->dpy_text_column, columns);
193 mcview_movement_fixups (view, FALSE);
194 }
195
196 /* --------------------------------------------------------------------------------------------- */
197
198 void
mcview_move_right(WView * view,off_t columns)199 mcview_move_right (WView * view, off_t columns)
200 {
201 if (view->mode_flags.hex)
202 {
203 off_t last_byte;
204 off_t old_cursor = view->hex_cursor;
205
206 last_byte = mcview_get_filesize (view);
207 last_byte = DOZ (last_byte, 1);
208
209 g_assert (columns == 1);
210
211 if (view->hexview_in_text || view->hexedit_lownibble)
212 {
213 if (view->hex_cursor < last_byte)
214 view->hex_cursor++;
215 }
216 if (!view->hexview_in_text)
217 if (old_cursor < last_byte || !view->hexedit_lownibble)
218 view->hexedit_lownibble = !view->hexedit_lownibble;
219 }
220 else if (!view->mode_flags.wrap)
221 {
222 view->dpy_text_column += columns;
223 }
224 mcview_movement_fixups (view, FALSE);
225 }
226
227 /* --------------------------------------------------------------------------------------------- */
228
229 void
mcview_moveto_top(WView * view)230 mcview_moveto_top (WView * view)
231 {
232 view->dpy_start = 0;
233 view->dpy_paragraph_skip_lines = 0;
234 mcview_state_machine_init (&view->dpy_state_top, 0);
235 view->hex_cursor = 0;
236 view->dpy_text_column = 0;
237 mcview_movement_fixups (view, TRUE);
238 }
239
240 /* --------------------------------------------------------------------------------------------- */
241
242 void
mcview_moveto_bottom(WView * view)243 mcview_moveto_bottom (WView * view)
244 {
245 off_t filesize;
246
247 mcview_update_filesize (view);
248
249 if (view->growbuf_in_use)
250 mcview_growbuf_read_all_data (view);
251
252 filesize = mcview_get_filesize (view);
253
254 if (view->mode_flags.hex)
255 {
256 view->hex_cursor = DOZ (filesize, 1);
257 mcview_movement_fixups (view, TRUE);
258 }
259 else
260 {
261 const off_t datalines = view->data_area.height;
262
263 view->dpy_start = filesize;
264 view->dpy_paragraph_skip_lines = 0;
265 view->dpy_wrap_dirty = TRUE;
266 mcview_move_up (view, datalines);
267 }
268 }
269
270 /* --------------------------------------------------------------------------------------------- */
271
272 void
mcview_moveto_bol(WView * view)273 mcview_moveto_bol (WView * view)
274 {
275 if (view->mode_flags.hex)
276 {
277 view->hex_cursor -= view->hex_cursor % view->bytes_per_line;
278 view->dpy_text_column = 0;
279 }
280 else
281 {
282 mcview_ascii_moveto_bol (view);
283 }
284 mcview_movement_fixups (view, TRUE);
285 }
286
287 /* --------------------------------------------------------------------------------------------- */
288
289 void
mcview_moveto_eol(WView * view)290 mcview_moveto_eol (WView * view)
291 {
292 off_t bol;
293
294 if (view->mode_flags.hex)
295 {
296 off_t filesize;
297
298 bol = mcview_offset_rounddown (view->hex_cursor, view->bytes_per_line);
299 if (mcview_get_byte_indexed (view, bol, view->bytes_per_line - 1, NULL) == TRUE)
300 {
301 view->hex_cursor = bol + view->bytes_per_line - 1;
302 }
303 else
304 {
305 filesize = mcview_get_filesize (view);
306 view->hex_cursor = DOZ (filesize, 1);
307 }
308 }
309 else
310 {
311 mcview_ascii_moveto_eol (view);
312 }
313 mcview_movement_fixups (view, FALSE);
314 }
315
316 /* --------------------------------------------------------------------------------------------- */
317
318 void
mcview_moveto_offset(WView * view,off_t offset)319 mcview_moveto_offset (WView * view, off_t offset)
320 {
321 if (view->mode_flags.hex)
322 {
323 view->hex_cursor = offset;
324 view->dpy_start = offset - offset % view->bytes_per_line;
325 view->dpy_paragraph_skip_lines = 0;
326 view->dpy_wrap_dirty = TRUE;
327 }
328 else
329 {
330 view->dpy_start = offset;
331 view->dpy_paragraph_skip_lines = 0;
332 view->dpy_wrap_dirty = TRUE;
333 }
334 mcview_movement_fixups (view, TRUE);
335 }
336
337 /* --------------------------------------------------------------------------------------------- */
338
339 void
mcview_moveto(WView * view,off_t line,off_t col)340 mcview_moveto (WView * view, off_t line, off_t col)
341 {
342 off_t offset;
343
344 mcview_coord_to_offset (view, &offset, line, col);
345 mcview_moveto_offset (view, offset);
346 }
347
348 /* --------------------------------------------------------------------------------------------- */
349
350 void
mcview_coord_to_offset(WView * view,off_t * ret_offset,off_t line,off_t column)351 mcview_coord_to_offset (WView * view, off_t * ret_offset, off_t line, off_t column)
352 {
353 coord_cache_entry_t coord;
354
355 coord.cc_line = line;
356 coord.cc_column = column;
357 coord.cc_nroff_column = column;
358 mcview_ccache_lookup (view, &coord, CCACHE_OFFSET);
359 *ret_offset = coord.cc_offset;
360 }
361
362 /* --------------------------------------------------------------------------------------------- */
363
364 void
mcview_offset_to_coord(WView * view,off_t * ret_line,off_t * ret_column,off_t offset)365 mcview_offset_to_coord (WView * view, off_t * ret_line, off_t * ret_column, off_t offset)
366 {
367 coord_cache_entry_t coord;
368
369 coord.cc_offset = offset;
370 mcview_ccache_lookup (view, &coord, CCACHE_LINECOL);
371
372 *ret_line = coord.cc_line;
373 *ret_column = view->mode_flags.nroff ? coord.cc_nroff_column : coord.cc_column;
374 }
375
376 /* --------------------------------------------------------------------------------------------- */
377
378 void
mcview_place_cursor(WView * view)379 mcview_place_cursor (WView * view)
380 {
381 const screen_dimen top = view->data_area.top;
382 const screen_dimen left = view->data_area.left;
383 screen_dimen col = view->cursor_col;
384 if (!view->hexview_in_text && view->hexedit_lownibble)
385 col++;
386 widget_gotoyx (view, top + view->cursor_row, left + col);
387 }
388
389 /* --------------------------------------------------------------------------------------------- */
390 /** we have set view->search_start and view->search_end and must set
391 * view->dpy_text_column and view->dpy_start
392 * try to display maximum of match */
393
394 void
mcview_moveto_match(WView * view)395 mcview_moveto_match (WView * view)
396 {
397 if (view->mode_flags.hex)
398 {
399 view->hex_cursor = view->search_start;
400 view->hexedit_lownibble = FALSE;
401 view->dpy_start = view->search_start - view->search_start % view->bytes_per_line;
402 view->dpy_end = view->search_end - view->search_end % view->bytes_per_line;
403 view->dpy_paragraph_skip_lines = 0;
404 view->dpy_wrap_dirty = TRUE;
405 }
406 else
407 {
408 view->dpy_start = mcview_bol (view, view->search_start, 0);
409 view->dpy_paragraph_skip_lines = 0;
410 view->dpy_wrap_dirty = TRUE;
411 }
412
413 mcview_scroll_to_cursor (view);
414 view->dirty++;
415 }
416
417 /* --------------------------------------------------------------------------------------------- */
418