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