1 /*
2 Internal file viewer for the Midnight Commander
3 Function for work with coordinate cache (ccache)
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
18 Ilia Maslakov <il.smind@gmail.com>, 2009
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 This cache provides you with a fast lookup to map file offsets into
38 line/column pairs and vice versa. The interface to the mapping is
39 provided by the functions mcview_coord_to_offset() and
40 mcview_offset_to_coord().
41
42 The cache is implemented as a simple sorted array holding entries
43 that map some of the offsets to their line/column pair. Entries that
44 are not cached themselves are interpolated (exactly) from their
45 neighbor entries. The algorithm used for determining the line/column
46 for a specific offset needs to be kept synchronized with the one used
47 in display().
48 */
49
50 #include <config.h>
51
52 #include <string.h> /* memmove() */
53 #ifdef MC_ENABLE_DEBUGGING_CODE
54 #include <inttypes.h> /* uintmax_t */
55 #endif
56
57 #include "lib/global.h"
58 #include "lib/tty/tty.h"
59 #include "internal.h"
60
61 /*** global variables ****************************************************************************/
62
63 /*** file scope macro definitions ****************************************************************/
64
65 #define VIEW_COORD_CACHE_GRANUL 1024
66 #define CACHE_CAPACITY_DELTA 64
67
68 /*** file scope type declarations ****************************************************************/
69
70 typedef gboolean (*cmp_func_t) (const coord_cache_entry_t * a, const coord_cache_entry_t * b);
71
72 /*** file scope variables ************************************************************************/
73
74 /*** file scope functions ************************************************************************/
75 /* --------------------------------------------------------------------------------------------- */
76
77 /* insert new cache entry into the cache */
78 static void
mcview_ccache_add_entry(coord_cache_t * cache,size_t pos,const coord_cache_entry_t * entry)79 mcview_ccache_add_entry (coord_cache_t * cache, size_t pos, const coord_cache_entry_t * entry)
80 {
81 if ((cache == NULL) || (entry == NULL))
82 return;
83
84 pos = MIN (pos, cache->size);
85
86 /* increase cache capacity if needed */
87 if (cache->size == cache->capacity)
88 {
89 cache->capacity += CACHE_CAPACITY_DELTA;
90 cache->cache = g_realloc (cache->cache, cache->capacity * sizeof (*cache->cache));
91 }
92
93 /* insert new entry */
94 if (pos != cache->size)
95 memmove (cache->cache[pos + 1], cache->cache[pos],
96 (cache->size - pos) * sizeof (*cache->cache));
97 cache->cache[pos] = g_memdup (entry, sizeof (*entry));
98 cache->size++;
99 }
100
101 /* --------------------------------------------------------------------------------------------- */
102
103 static gboolean
mcview_coord_cache_entry_less_offset(const coord_cache_entry_t * a,const coord_cache_entry_t * b)104 mcview_coord_cache_entry_less_offset (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
105 {
106 return (a->cc_offset < b->cc_offset);
107 }
108
109 /* --------------------------------------------------------------------------------------------- */
110
111 static gboolean
mcview_coord_cache_entry_less_plain(const coord_cache_entry_t * a,const coord_cache_entry_t * b)112 mcview_coord_cache_entry_less_plain (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
113 {
114 if (a->cc_line < b->cc_line)
115 return TRUE;
116
117 if (a->cc_line == b->cc_line)
118 return (a->cc_column < b->cc_column);
119
120 return FALSE;
121 }
122
123
124 /* --------------------------------------------------------------------------------------------- */
125
126 static gboolean
mcview_coord_cache_entry_less_nroff(const coord_cache_entry_t * a,const coord_cache_entry_t * b)127 mcview_coord_cache_entry_less_nroff (const coord_cache_entry_t * a, const coord_cache_entry_t * b)
128 {
129 if (a->cc_line < b->cc_line)
130 return TRUE;
131
132 if (a->cc_line == b->cc_line)
133 return (a->cc_nroff_column < b->cc_nroff_column);
134
135 return FALSE;
136 }
137
138
139 /* --------------------------------------------------------------------------------------------- */
140 /** Find and return the index of the last cache entry that is
141 * smaller than ''coord'', according to the criterion ''sort_by''. */
142
143 static inline size_t
mcview_ccache_find(WView * view,const coord_cache_entry_t * coord,cmp_func_t cmp_func)144 mcview_ccache_find (WView * view, const coord_cache_entry_t * coord, cmp_func_t cmp_func)
145 {
146 size_t base = 0;
147 size_t limit = view->coord_cache->size;
148
149 g_assert (limit != 0);
150
151 while (limit > 1)
152 {
153 size_t i;
154
155 i = base + limit / 2;
156 if (cmp_func (coord, view->coord_cache->cache[i]))
157 {
158 /* continue the search in the lower half of the cache */
159 }
160 else
161 {
162 /* continue the search in the upper half of the cache */
163 base = i;
164 }
165 limit = (limit + 1) / 2;
166 }
167 return base;
168 }
169
170 /* --------------------------------------------------------------------------------------------- */
171 /*** public functions ****************************************************************************/
172 /* --------------------------------------------------------------------------------------------- */
173
174 coord_cache_t *
coord_cache_new(void)175 coord_cache_new (void)
176 {
177 coord_cache_t *cache;
178
179 cache = g_new (coord_cache_t, 1);
180 cache->size = 0;
181 cache->capacity = CACHE_CAPACITY_DELTA;
182 cache->cache = g_malloc0 (cache->capacity * sizeof (*cache->cache));
183
184 return cache;
185 }
186
187 /* --------------------------------------------------------------------------------------------- */
188
189 void
coord_cache_free(coord_cache_t * cache)190 coord_cache_free (coord_cache_t * cache)
191 {
192 if (cache != NULL)
193 {
194 size_t i;
195
196 for (i = 0; i < cache->size; i++)
197 g_free (cache->cache[i]);
198
199 g_free (cache->cache);
200 g_free (cache);
201 }
202 }
203
204 /* --------------------------------------------------------------------------------------------- */
205
206 #ifdef MC_ENABLE_DEBUGGING_CODE
207
208 void
mcview_ccache_dump(WView * view)209 mcview_ccache_dump (WView * view)
210 {
211 FILE *f;
212 off_t offset, line, column, nextline_offset, filesize;
213 guint i;
214 const coord_cache_t *cache = view->coord_cache;
215
216 g_assert (cache != NULL);
217
218 filesize = mcview_get_filesize (view);
219
220 f = fopen ("mcview-ccache.out", "w");
221 if (f == NULL)
222 return;
223 (void) setvbuf (f, NULL, _IONBF, 0);
224
225 /* cache entries */
226 for (i = 0; i < cache->size; i++)
227 {
228 (void) fprintf (f,
229 "entry %8u offset %8" PRIuMAX
230 " line %8" PRIuMAX " column %8" PRIuMAX
231 " nroff_column %8" PRIuMAX "\n",
232 (unsigned int) i,
233 (uintmax_t) cache->cache[i]->cc_offset,
234 (uintmax_t) cache->cache[i]->cc_line,
235 (uintmax_t) cache->cache[i]->cc_column,
236 (uintmax_t) cache->cache[i]->cc_nroff_column);
237 }
238 (void) fprintf (f, "\n");
239
240 /* offset -> line/column translation */
241 for (offset = 0; offset < filesize; offset++)
242 {
243 mcview_offset_to_coord (view, &line, &column, offset);
244 (void) fprintf (f,
245 "offset %8" PRIuMAX " line %8" PRIuMAX " column %8" PRIuMAX "\n",
246 (uintmax_t) offset, (uintmax_t) line, (uintmax_t) column);
247 }
248
249 /* line/column -> offset translation */
250 for (line = 0; TRUE; line++)
251 {
252 mcview_coord_to_offset (view, &nextline_offset, line + 1, 0);
253 (void) fprintf (f, "nextline_offset %8" PRIuMAX "\n", (uintmax_t) nextline_offset);
254
255 for (column = 0; TRUE; column++)
256 {
257 mcview_coord_to_offset (view, &offset, line, column);
258 if (offset >= nextline_offset)
259 break;
260
261 (void) fprintf (f,
262 "line %8" PRIuMAX " column %8" PRIuMAX " offset %8" PRIuMAX "\n",
263 (uintmax_t) line, (uintmax_t) column, (uintmax_t) offset);
264 }
265
266 if (nextline_offset >= filesize - 1)
267 break;
268 }
269
270 (void) fclose (f);
271 }
272 #endif
273
274 /* --------------------------------------------------------------------------------------------- */
275 /** Look up the missing components of ''coord'', which are given by
276 * ''lookup_what''. The function returns the smallest value that
277 * matches the existing components of ''coord''.
278 */
279
280 void
mcview_ccache_lookup(WView * view,coord_cache_entry_t * coord,enum ccache_type lookup_what)281 mcview_ccache_lookup (WView * view, coord_cache_entry_t * coord, enum ccache_type lookup_what)
282 {
283 size_t i;
284 coord_cache_t *cache;
285 coord_cache_entry_t current, next, entry;
286 enum ccache_type sorter;
287 off_t limit;
288 cmp_func_t cmp_func;
289
290 enum
291 {
292 NROFF_START,
293 NROFF_BACKSPACE,
294 NROFF_CONTINUATION
295 } nroff_state;
296
297 if (view->coord_cache == NULL)
298 view->coord_cache = coord_cache_new ();
299
300 cache = view->coord_cache;
301
302 if (cache->size == 0)
303 {
304 current.cc_offset = 0;
305 current.cc_line = 0;
306 current.cc_column = 0;
307 current.cc_nroff_column = 0;
308 mcview_ccache_add_entry (cache, 0, ¤t);
309 }
310
311 sorter = (lookup_what == CCACHE_OFFSET) ? CCACHE_LINECOL : CCACHE_OFFSET;
312
313 if (sorter == CCACHE_OFFSET)
314 cmp_func = mcview_coord_cache_entry_less_offset;
315 else if (view->mode_flags.nroff)
316 cmp_func = mcview_coord_cache_entry_less_nroff;
317 else
318 cmp_func = mcview_coord_cache_entry_less_plain;
319
320
321 tty_enable_interrupt_key ();
322
323 retry:
324 /* find the two neighbor entries in the cache */
325 i = mcview_ccache_find (view, coord, cmp_func);
326 /* now i points to the lower neighbor in the cache */
327
328 current = *cache->cache[i];
329 if (i + 1 < view->coord_cache->size)
330 limit = cache->cache[i + 1]->cc_offset;
331 else
332 limit = current.cc_offset + VIEW_COORD_CACHE_GRANUL;
333
334 entry = current;
335 nroff_state = NROFF_START;
336 for (; current.cc_offset < limit; current = next)
337 {
338 int c;
339
340 if (!mcview_get_byte (view, current.cc_offset, &c))
341 break;
342
343 if (!cmp_func (¤t, coord))
344 {
345 if (lookup_what == CCACHE_OFFSET && view->mode_flags.nroff
346 && nroff_state != NROFF_START)
347 {
348 /* don't break here */
349 }
350 else
351 {
352 break;
353 }
354 }
355
356 /* Provide useful default values for ''next'' */
357 next.cc_offset = current.cc_offset + 1;
358 next.cc_line = current.cc_line;
359 next.cc_column = current.cc_column + 1;
360 next.cc_nroff_column = current.cc_nroff_column + 1;
361
362 /* and override some of them as necessary. */
363 if (c == '\r')
364 {
365 int nextc = -1;
366
367 mcview_get_byte_indexed (view, current.cc_offset, 1, &nextc);
368
369 /* Ignore '\r' if it is followed by '\r' or '\n'. If it is
370 * followed by anything else, it is a Mac line ending and
371 * produces a line break.
372 */
373 if (nextc == '\r' || nextc == '\n')
374 {
375 next.cc_column = current.cc_column;
376 next.cc_nroff_column = current.cc_nroff_column;
377 }
378 else
379 {
380 next.cc_line = current.cc_line + 1;
381 next.cc_column = 0;
382 next.cc_nroff_column = 0;
383 }
384
385 }
386 else if (nroff_state == NROFF_BACKSPACE)
387 {
388 next.cc_nroff_column = current.cc_nroff_column - 1;
389
390 }
391 else if (c == '\t')
392 {
393 next.cc_column = mcview_offset_rounddown (current.cc_column, 8) + 8;
394 next.cc_nroff_column = mcview_offset_rounddown (current.cc_nroff_column, 8) + 8;
395
396 }
397 else if (c == '\n')
398 {
399 next.cc_line = current.cc_line + 1;
400 next.cc_column = 0;
401 next.cc_nroff_column = 0;
402
403 }
404 else
405 {
406 /* Use all default values from above */
407 }
408
409 switch (nroff_state)
410 {
411 case NROFF_START:
412 case NROFF_CONTINUATION:
413 nroff_state = mcview_is_nroff_sequence (view, current.cc_offset)
414 ? NROFF_BACKSPACE : NROFF_START;
415 break;
416 case NROFF_BACKSPACE:
417 nroff_state = NROFF_CONTINUATION;
418 break;
419 default:
420 break;
421 }
422
423 /* Cache entries must guarantee that for each i < j,
424 * line[i] <= line[j] and column[i] < column[j]. In the case of
425 * nroff sequences and '\r' characters, this is not guaranteed,
426 * so we cannot save them. */
427 if (nroff_state == NROFF_START && c != '\r')
428 entry = next;
429 }
430
431 if (i + 1 == cache->size && entry.cc_offset != cache->cache[i]->cc_offset)
432 {
433 mcview_ccache_add_entry (cache, cache->size, &entry);
434
435 if (!tty_got_interrupt ())
436 goto retry;
437 }
438
439 tty_disable_interrupt_key ();
440
441 if (lookup_what == CCACHE_OFFSET)
442 {
443 coord->cc_offset = current.cc_offset;
444 }
445 else
446 {
447 coord->cc_line = current.cc_line;
448 coord->cc_column = current.cc_column;
449 coord->cc_nroff_column = current.cc_nroff_column;
450 }
451 }
452
453 /* --------------------------------------------------------------------------------------------- */
454