1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "flist_pos.h"
21 
22 #include <assert.h> /* assert() */
23 #include <stddef.h> /* NULL size_t */
24 #include <stdlib.h> /* abs() */
25 #include <string.h> /* strcmp() */
26 #include <wctype.h> /* towupper() */
27 
28 #include "cfg/config.h"
29 #include "ui/fileview.h"
30 #include "ui/ui.h"
31 #include "utils/fs.h"
32 #include "utils/regexp.h"
33 #include "utils/path.h"
34 #include "utils/str.h"
35 #include "utils/utf8.h"
36 #include "utils/utils.h"
37 #include "filelist.h"
38 #include "filtering.h"
39 #include "types.h"
40 
41 static int get_curr_col(const view_t *view);
42 static int get_curr_line(const view_t *view);
43 static int get_max_col(const view_t *view);
44 static int get_max_line(const view_t *view);
45 static int get_column_top_pos(const view_t *view);
46 static int get_column_bottom_pos(const view_t *view);
47 static const char * get_last_ext(const char name[]);
48 static int is_mismatched_entry(const dir_entry_t *entry);
49 static int find_next(const view_t *view, entry_predicate pred);
50 static int find_prev(const view_t *view, entry_predicate pred);
51 static int file_can_be_displayed(const char directory[], const char filename[]);
52 
53 int
fpos_find_by_name(const view_t * view,const char name[])54 fpos_find_by_name(const view_t *view, const char name[])
55 {
56 	return fpos_find_entry(view, name, NULL);
57 }
58 
59 int
fpos_find_entry(const view_t * view,const char name[],const char dir[])60 fpos_find_entry(const view_t *view, const char name[], const char dir[])
61 {
62 	int i;
63 	for(i = 0; i < view->list_rows; ++i)
64 	{
65 		if(dir != NULL && stroscmp(view->dir_entry[i].origin, dir) != 0)
66 		{
67 			continue;
68 		}
69 
70 		if(stroscmp(view->dir_entry[i].name, name) == 0)
71 		{
72 			return i;
73 		}
74 	}
75 	return -1;
76 }
77 
78 int
fpos_scroll_down(view_t * view,int lines_count)79 fpos_scroll_down(view_t *view, int lines_count)
80 {
81 	if(!fpos_are_all_files_visible(view))
82 	{
83 		view->list_pos =
84 			get_corrected_list_pos_down(view, lines_count*view->run_size);
85 		return 1;
86 	}
87 	return 0;
88 }
89 
90 int
fpos_scroll_up(view_t * view,int lines_count)91 fpos_scroll_up(view_t *view, int lines_count)
92 {
93 	if(!fpos_are_all_files_visible(view))
94 	{
95 		view->list_pos =
96 			get_corrected_list_pos_up(view, lines_count*view->run_size);
97 		return 1;
98 	}
99 	return 0;
100 }
101 
102 void
fpos_set_pos(view_t * view,int pos)103 fpos_set_pos(view_t *view, int pos)
104 {
105 	if(pos < 1)
106 	{
107 		pos = 0;
108 	}
109 
110 	if(pos > view->list_rows - 1)
111 	{
112 		pos = view->list_rows - 1;
113 	}
114 
115 	if(pos != -1)
116 	{
117 		view_t *const other = (view == curr_view) ? other_view : curr_view;
118 
119 		view->list_pos = pos;
120 		fview_position_updated(view);
121 
122 		/* Synchronize cursor with the other pane. */
123 		if(view->custom.type == CV_DIFF && other->list_pos != pos)
124 		{
125 			fpos_set_pos(other, pos);
126 		}
127 	}
128 }
129 
130 void
fpos_ensure_valid_pos(view_t * view)131 fpos_ensure_valid_pos(view_t *view)
132 {
133 	if(view->list_pos < 0)
134 	{
135 		view->list_pos = 0;
136 	}
137 
138 	if(view->list_pos >= view->list_rows)
139 	{
140 		view->list_pos = view->list_rows - 1;
141 	}
142 }
143 
144 int
fpos_get_col(const view_t * view,int pos)145 fpos_get_col(const view_t *view, int pos)
146 {
147 	return (fview_is_transposed(view) ? pos/view->run_size : pos%view->run_size);
148 }
149 
150 int
fpos_get_line(const view_t * view,int pos)151 fpos_get_line(const view_t *view, int pos)
152 {
153 	return (fview_is_transposed(view) ? pos%view->run_size : pos/view->run_size);
154 }
155 
156 int
fpos_can_move_left(const view_t * view)157 fpos_can_move_left(const view_t *view)
158 {
159 	return (view->list_pos > 0);
160 }
161 
162 int
fpos_can_move_right(const view_t * view)163 fpos_can_move_right(const view_t *view)
164 {
165 	return (view->list_pos < view->list_rows - 1);
166 }
167 
168 int
fpos_can_move_up(const view_t * view)169 fpos_can_move_up(const view_t *view)
170 {
171 	return fview_is_transposed(view) ? view->list_pos > 0
172 	                                 : view->list_pos >= view->run_size;
173 }
174 
175 int
fpos_can_move_down(const view_t * view)176 fpos_can_move_down(const view_t *view)
177 {
178 	return fview_is_transposed(view) ? view->list_pos < view->list_rows - 1
179 	                                 : get_curr_line(view) < get_max_line(view);
180 }
181 
182 int
fpos_at_first_col(const view_t * view)183 fpos_at_first_col(const view_t *view)
184 {
185 	return (get_curr_col(view) == 0);
186 }
187 
188 int
fpos_at_last_col(const view_t * view)189 fpos_at_last_col(const view_t *view)
190 {
191 	return (get_curr_col(view) == get_max_col(view));
192 }
193 
194 /* Retrieves column number of cursor.  Returns the number. */
195 static int
get_curr_col(const view_t * view)196 get_curr_col(const view_t *view)
197 {
198 	return fpos_get_col(view, view->list_pos);
199 }
200 
201 /* Retrieves line number of cursor.  Returns the number. */
202 static int
get_curr_line(const view_t * view)203 get_curr_line(const view_t *view)
204 {
205 	return fpos_get_line(view, view->list_pos);
206 }
207 
208 /* Retrieves maximum column number.  Returns the number. */
209 static int
get_max_col(const view_t * view)210 get_max_col(const view_t *view)
211 {
212 	return fview_is_transposed(view) ? (view->list_rows - 1)/view->run_size
213 	                                 : (view->run_size - 1);
214 }
215 
216 /* Retrieves maximum line number.  Returns the number. */
217 static int
get_max_line(const view_t * view)218 get_max_line(const view_t *view)
219 {
220 	return fview_is_transposed(view) ? (view->run_size - 1)
221 	                                 : (view->list_rows - 1)/view->run_size;
222 }
223 
224 int
fpos_line_start(const view_t * view)225 fpos_line_start(const view_t *view)
226 {
227 	return fview_is_transposed(view) ? view->list_pos%view->run_size
228 	                                 : ROUND_DOWN(view->list_pos, view->run_size);
229 }
230 
231 int
fpos_line_end(const view_t * view)232 fpos_line_end(const view_t *view)
233 {
234 	if(fview_is_transposed(view))
235 	{
236 		const int last_top_pos = ROUND_DOWN(view->list_rows - 1, view->run_size);
237 		const int pos = last_top_pos + view->list_pos%view->run_size;
238 		return (pos < view->list_rows ? pos : pos - view->run_size);
239 	}
240 
241 	return MIN(view->list_rows - 1, fpos_line_start(view) + view->run_size - 1);
242 }
243 
244 int
fpos_get_hor_step(const struct view_t * view)245 fpos_get_hor_step(const struct view_t *view)
246 {
247 	return (fview_is_transposed(view) ? view->run_size : 1);
248 }
249 
250 int
fpos_get_ver_step(const struct view_t * view)251 fpos_get_ver_step(const struct view_t *view)
252 {
253 	return (fview_is_transposed(view) ? 1 : view->run_size);
254 }
255 
256 int
fpos_has_hidden_top(const view_t * view)257 fpos_has_hidden_top(const view_t *view)
258 {
259 	return (fview_is_transposed(view) ? 0 : can_scroll_up(view));
260 }
261 
262 int
fpos_has_hidden_bottom(const view_t * view)263 fpos_has_hidden_bottom(const view_t *view)
264 {
265 	return (fview_is_transposed(view) ? 0 : can_scroll_down(view));
266 }
267 
268 int
fpos_get_top_pos(const view_t * view)269 fpos_get_top_pos(const view_t *view)
270 {
271 	return get_column_top_pos(view)
272 	     + (can_scroll_up(view) ? fpos_get_offset(view) : 0);
273 }
274 
275 int
fpos_get_middle_pos(const view_t * view)276 fpos_get_middle_pos(const view_t *view)
277 {
278 	const int top_pos = get_column_top_pos(view);
279 	const int bottom_pos = get_column_bottom_pos(view);
280 	const int v = (fview_is_transposed(view) ? 1 : view->run_size);
281 	return top_pos + (DIV_ROUND_UP(bottom_pos - top_pos, v)/2)*v;
282 }
283 
284 int
fpos_get_bottom_pos(const view_t * view)285 fpos_get_bottom_pos(const view_t *view)
286 {
287 	return get_column_bottom_pos(view)
288 	     - (can_scroll_down(view) ? fpos_get_offset(view) : 0);
289 }
290 
291 int
fpos_get_offset(const view_t * view)292 fpos_get_offset(const view_t *view)
293 {
294 	int val;
295 
296 	if(fview_is_transposed(view))
297 	{
298 		/* Scroll offset doesn't make much sense for transposed table. */
299 		return 0;
300 	}
301 
302 	val = MIN(DIV_ROUND_UP(view->window_rows - 1, 2), MAX(cfg.scroll_off, 0));
303 	return val*view->column_count;
304 }
305 
306 int
fpos_are_all_files_visible(const view_t * view)307 fpos_are_all_files_visible(const view_t *view)
308 {
309 	return view->list_rows <= view->window_cells;
310 }
311 
312 int
fpos_get_last_visible_cell(const view_t * view)313 fpos_get_last_visible_cell(const view_t *view)
314 {
315 	return view->top_line + view->window_cells - 1;
316 }
317 
318 int
fpos_half_scroll(view_t * view,int down)319 fpos_half_scroll(view_t *view, int down)
320 {
321 	int new_pos;
322 
323 	int offset = MAX(view->window_cells/2, view->run_size);
324 	offset = ROUND_DOWN(offset, curr_view->run_size);
325 
326 	if(down)
327 	{
328 		new_pos = get_corrected_list_pos_down(view, offset);
329 		new_pos = MAX(new_pos, view->list_pos + offset);
330 
331 		if(new_pos >= view->list_rows)
332 		{
333 			new_pos -= view->column_count*
334 				DIV_ROUND_UP(new_pos - (view->list_rows - 1), view->column_count);
335 		}
336 	}
337 	else
338 	{
339 		new_pos = get_corrected_list_pos_up(view, offset);
340 		new_pos = MIN(new_pos, view->list_pos - offset);
341 
342 		if(new_pos < 0)
343 		{
344 			new_pos += view->column_count*DIV_ROUND_UP(-new_pos, view->column_count);
345 		}
346 	}
347 
348 	scroll_by_files(view, new_pos - view->list_pos);
349 	return new_pos;
350 }
351 
352 /* Retrieves position of a file at the top of visible part of current column.
353  * Returns the position. */
354 static int
get_column_top_pos(const view_t * view)355 get_column_top_pos(const view_t *view)
356 {
357 	const int column_correction = fview_is_transposed(view)
358 	  ? ROUND_DOWN(view->list_pos - view->top_line, view->run_size)
359 	  : view->list_pos%view->run_size;
360 	return view->top_line + column_correction;
361 }
362 
363 /* Retrieves position of a file at the bottom of visible part of current column.
364  * Returns the position. */
365 static int
get_column_bottom_pos(const view_t * view)366 get_column_bottom_pos(const view_t *view)
367 {
368 	if(fview_is_transposed(view))
369 	{
370 		const int top_pos = get_column_top_pos(view);
371 		const int last = view->list_rows - 1;
372 		return MIN(top_pos + view->window_rows - 1, last);
373 	}
374 	else
375 	{
376 		const int last_top_pos =
377 			ROUND_DOWN(MIN(fpos_get_last_visible_cell(view), view->list_rows - 1),
378 					view->run_size);
379 		const int pos = last_top_pos + view->list_pos%view->run_size;
380 		return (pos < view->list_rows ? pos : pos - view->run_size);
381 	}
382 }
383 
384 int
fpos_find_group(const view_t * view,int next)385 fpos_find_group(const view_t *view, int next)
386 {
387 	/* TODO: refactor/simplify this function (fpos_find_group()). */
388 
389 	const int correction = next ? -1 : 0;
390 	const int lb = correction;
391 	const int ub = view->list_rows + correction;
392 	const int inc = next ? +1 : -1;
393 
394 	int pos = view->list_pos;
395 	dir_entry_t *pentry = &view->dir_entry[pos];
396 	const char *ext = get_last_ext(pentry->name);
397 	size_t char_width = utf8_chrw(pentry->name);
398 	wchar_t ch = towupper(get_first_wchar(pentry->name));
399 	const SortingKey sorting_key =
400 		flist_custom_active(view) && cv_compare(view->custom.type)
401 		? SK_BY_ID
402 		: abs(view->sort[0]);
403 	const int is_dir = fentry_is_dir(pentry);
404 	const char *const type_str = get_type_str(pentry->type);
405 	regmatch_t pmatch = { .rm_so = 0, .rm_eo = 0 };
406 #ifndef _WIN32
407 	char perms[16];
408 	get_perm_string(perms, sizeof(perms), pentry->mode);
409 #endif
410 	if(sorting_key == SK_BY_GROUPS)
411 	{
412 		pmatch = get_group_match(&view->primary_group, pentry->name);
413 	}
414 	while(pos > lb && pos < ub)
415 	{
416 		dir_entry_t *nentry;
417 		pos += inc;
418 		nentry = &view->dir_entry[pos];
419 		switch(sorting_key)
420 		{
421 			case SK_BY_FILEEXT:
422 				if(fentry_is_dir(nentry))
423 				{
424 					if(strncmp(pentry->name, nentry->name, char_width) != 0)
425 					{
426 						return pos;
427 					}
428 				}
429 				if(strcmp(get_last_ext(nentry->name), ext) != 0)
430 				{
431 					return pos;
432 				}
433 				break;
434 			case SK_BY_EXTENSION:
435 				if(strcmp(get_last_ext(nentry->name), ext) != 0)
436 					return pos;
437 				break;
438 			case SK_BY_GROUPS:
439 				{
440 					regmatch_t nmatch = get_group_match(&view->primary_group,
441 							nentry->name);
442 
443 					if(pmatch.rm_eo - pmatch.rm_so != nmatch.rm_eo - nmatch.rm_so ||
444 							(pmatch.rm_eo != pmatch.rm_so &&
445 							 strncmp(pentry->name + pmatch.rm_so, nentry->name + nmatch.rm_so,
446 								 pmatch.rm_eo - pmatch.rm_so + 1U) != 0))
447 						return pos;
448 				}
449 				break;
450 			case SK_BY_TARGET:
451 				if((nentry->type == FT_LINK) != (pentry->type == FT_LINK))
452 				{
453 					/* One of the entries is not a link. */
454 					return pos;
455 				}
456 				if(nentry->type == FT_LINK)
457 				{
458 					/* Both entries are symbolic links. */
459 					char full_path[PATH_MAX + 1];
460 					char nlink[PATH_MAX + 1], plink[PATH_MAX + 1];
461 
462 					get_full_path_of(nentry, sizeof(full_path), full_path);
463 					if(get_link_target(full_path, nlink, sizeof(nlink)) != 0)
464 					{
465 						return pos;
466 					}
467 					get_full_path_of(pentry, sizeof(full_path), full_path);
468 					if(get_link_target(full_path, plink, sizeof(plink)) != 0)
469 					{
470 						return pos;
471 					}
472 
473 					if(stroscmp(nlink, plink) != 0)
474 					{
475 						return pos;
476 					}
477 				}
478 				break;
479 			case SK_BY_NAME:
480 				if(strncmp(pentry->name, nentry->name, char_width) != 0)
481 					return pos;
482 				break;
483 			case SK_BY_INAME:
484 				if((wchar_t)towupper(get_first_wchar(nentry->name)) != ch)
485 					return pos;
486 				break;
487 			case SK_BY_SIZE:
488 				if(nentry->size != pentry->size)
489 					return pos;
490 				break;
491 			case SK_BY_NITEMS:
492 				if(fentry_get_nitems(view, nentry) != fentry_get_nitems(view, pentry))
493 					return pos;
494 				break;
495 			case SK_BY_TIME_ACCESSED:
496 				if(nentry->atime != pentry->atime)
497 					return pos;
498 				break;
499 			case SK_BY_TIME_CHANGED:
500 				if(nentry->ctime != pentry->ctime)
501 					return pos;
502 				break;
503 			case SK_BY_TIME_MODIFIED:
504 				if(nentry->mtime != pentry->mtime)
505 					return pos;
506 				break;
507 			case SK_BY_DIR:
508 				if(is_dir != fentry_is_dir(nentry))
509 				{
510 					return pos;
511 				}
512 				break;
513 			case SK_BY_TYPE:
514 				if(get_type_str(nentry->type) != type_str)
515 				{
516 					return pos;
517 				}
518 				break;
519 #ifndef _WIN32
520 			case SK_BY_GROUP_NAME:
521 			case SK_BY_GROUP_ID:
522 				if(nentry->gid != pentry->gid)
523 					return pos;
524 				break;
525 			case SK_BY_OWNER_NAME:
526 			case SK_BY_OWNER_ID:
527 				if(nentry->uid != pentry->uid)
528 					return pos;
529 				break;
530 			case SK_BY_MODE:
531 				if(nentry->mode != pentry->mode)
532 					return pos;
533 				break;
534 			case SK_BY_INODE:
535 				if(nentry->inode != pentry->inode)
536 					return pos;
537 				break;
538 			case SK_BY_PERMISSIONS:
539 				{
540 					char nperms[16];
541 					get_perm_string(nperms, sizeof(nperms), nentry->mode);
542 					if(strcmp(nperms, perms) != 0)
543 					{
544 						return pos;
545 					}
546 					break;
547 				}
548 			case SK_BY_NLINKS:
549 				if(nentry->nlinks != pentry->nlinks)
550 				{
551 					return pos;
552 				}
553 				break;
554 #endif
555 		}
556 		/* Id sorting is builtin only and is defined outside SortingKey
557 		 * enumeration. */
558 		if((int)sorting_key == SK_BY_ID)
559 		{
560 			if(nentry->id != pentry->id)
561 			{
562 				return pos;
563 			}
564 		}
565 	}
566 	return pos;
567 }
568 
569 /* Finds pointer to the beginning of the last extension of the file name.
570  * Returns the pointer, which might point to the NUL byte if there are no
571  * extensions. */
572 static const char *
get_last_ext(const char name[])573 get_last_ext(const char name[])
574 {
575 	const char *const ext = strrchr(name, '.');
576 	return (ext == NULL) ? (name + strlen(name)) : (ext + 1);
577 }
578 
579 int
fpos_find_dir_group(const view_t * view,int next)580 fpos_find_dir_group(const view_t *view, int next)
581 {
582 	const int correction = next ? -1 : 0;
583 	const int lb = correction;
584 	const int ub = view->list_rows + correction;
585 	const int inc = next ? +1 : -1;
586 
587 	int pos = curr_view->list_pos;
588 	dir_entry_t *pentry = &curr_view->dir_entry[pos];
589 	const int is_dir = fentry_is_dir(pentry);
590 	while(pos > lb && pos < ub)
591 	{
592 		dir_entry_t *nentry;
593 		pos += inc;
594 		nentry = &curr_view->dir_entry[pos];
595 		if(is_dir != fentry_is_dir(nentry))
596 		{
597 			break;
598 		}
599 	}
600 	return pos;
601 }
602 
603 int
fpos_first_sibling(const view_t * view)604 fpos_first_sibling(const view_t *view)
605 {
606 	const int parent = view->list_pos - view->dir_entry[view->list_pos].child_pos;
607 	return (parent == view->list_pos ? 0 : parent + 1);
608 }
609 
610 int
fpos_last_sibling(const view_t * view)611 fpos_last_sibling(const view_t *view)
612 {
613 	int pos = view->list_pos - view->dir_entry[view->list_pos].child_pos;
614 	if(pos == view->list_pos)
615 	{
616 		/* For top-level entry, find the last top-level entry. */
617 		pos = view->list_rows - 1;
618 		while(view->dir_entry[pos].child_pos != 0)
619 		{
620 			pos -= view->dir_entry[pos].child_pos;
621 		}
622 	}
623 	else
624 	{
625 		/* For non-top-level entry, go to last tree item and go up until our
626 		 * child. */
627 		const int parent = pos;
628 		pos = parent + view->dir_entry[parent].child_count;
629 		while(pos - view->dir_entry[pos].child_pos != parent)
630 		{
631 			pos -= view->dir_entry[pos].child_pos;
632 		}
633 	}
634 	return pos;
635 }
636 
637 int
fpos_next_dir_sibling(const view_t * view)638 fpos_next_dir_sibling(const view_t *view)
639 {
640 	int pos = view->list_pos;
641 	const int parent = view->dir_entry[pos].child_pos == 0
642 	                 ? -1
643 	                 : pos - view->dir_entry[pos].child_pos;
644 	const int past_end = parent == -1
645 	                   ? view->list_rows
646 	                   : parent + view->dir_entry[parent].child_count;
647 	pos += view->dir_entry[pos].child_count + 1;
648 	while(pos < past_end)
649 	{
650 		dir_entry_t *const e = &view->dir_entry[pos];
651 		if(fentry_is_dir(e))
652 		{
653 			break;
654 		}
655 		/* Skip over whole sub-tree. */
656 		pos += e->child_count + 1;
657 	}
658 	return (pos < past_end ? pos : view->list_pos);
659 }
660 
661 int
fpos_prev_dir_sibling(const view_t * view)662 fpos_prev_dir_sibling(const view_t *view)
663 {
664 	int pos = view->list_pos;
665 	/* Determine original parent (-1 for top-most entry). */
666 	const int parent = view->dir_entry[pos].child_pos == 0
667 	                 ? -1
668 	                 : pos - view->dir_entry[pos].child_pos;
669 	--pos;
670 	while(pos > parent)
671 	{
672 		dir_entry_t *const e = &view->dir_entry[pos];
673 		const int p = (e->child_pos == 0) ? -1 : (pos - e->child_pos);
674 		/* If we find ourselves deeper than originally, just go up one level. */
675 		if(p != parent)
676 		{
677 			pos = p;
678 			continue;
679 		}
680 
681 		/* We're looking for directories. */
682 		if(fentry_is_dir(e))
683 		{
684 			break;
685 		}
686 		/* We're on a file on the same level. */
687 		--pos;
688 	}
689 	return (pos > parent ? pos : view->list_pos);
690 }
691 
692 int
fpos_next_dir(const view_t * view)693 fpos_next_dir(const view_t *view)
694 {
695 	return find_next(view, &fentry_is_dir);
696 }
697 
698 int
fpos_prev_dir(const view_t * view)699 fpos_prev_dir(const view_t *view)
700 {
701 	return find_prev(view, &fentry_is_dir);
702 }
703 
704 int
fpos_next_selected(const view_t * view)705 fpos_next_selected(const view_t *view)
706 {
707 	return find_next(view, &is_entry_selected);
708 }
709 
710 int
fpos_prev_selected(const view_t * view)711 fpos_prev_selected(const view_t *view)
712 {
713 	return find_prev(view, &is_entry_selected);
714 }
715 
716 int
fpos_next_mismatch(const view_t * view)717 fpos_next_mismatch(const view_t *view)
718 {
719 	return (view->custom.type == CV_DIFF)
720 	     ? find_next(view, &is_mismatched_entry)
721 	     : view->list_pos;
722 }
723 
724 int
fpos_prev_mismatch(const view_t * view)725 fpos_prev_mismatch(const view_t *view)
726 {
727 	return (view->custom.type == CV_DIFF)
728 	     ? find_prev(view, &is_mismatched_entry)
729 	     : view->list_pos;
730 }
731 
732 /* Checks whether entry corresponds to comparison mismatch.  Returns non-zero if
733  * so, otherwise zero is returned. */
734 static int
is_mismatched_entry(const dir_entry_t * entry)735 is_mismatched_entry(const dir_entry_t *entry)
736 {
737 	/* To avoid passing view pointer here, we exploit the fact that entry_to_pos()
738 	 * checks whether it's argument belongs to the given view. */
739 	int pos = entry_to_pos(&lwin, entry);
740 	view_t *other = &rwin;
741 	if(pos == -1)
742 	{
743 		pos = entry_to_pos(&rwin, entry);
744 		other = &lwin;
745 	}
746 
747 	return (other->dir_entry[pos].id != entry->id);
748 }
749 
750 /* Finds position of the next entry matching the predicate.  Returns new
751  * position which isn't changed if no next directory is found. */
752 static int
find_next(const view_t * view,entry_predicate pred)753 find_next(const view_t *view, entry_predicate pred)
754 {
755 	int pos = view->list_pos;
756 	while(++pos < view->list_rows)
757 	{
758 		if(pred(&view->dir_entry[pos]))
759 		{
760 			break;
761 		}
762 	}
763 	return (pos == view->list_rows ? view->list_pos : pos);
764 }
765 
766 /* Finds position of the previous entry matching the predicate.  Returns new
767  * position which isn't changed if no previous directory is found. */
768 static int
find_prev(const view_t * view,entry_predicate pred)769 find_prev(const view_t *view, entry_predicate pred)
770 {
771 	int pos = view->list_pos;
772 	while(--pos >= 0)
773 	{
774 		if(pred(&view->dir_entry[pos]))
775 		{
776 			break;
777 		}
778 	}
779 	return (pos < 0 ? view->list_pos : pos);
780 }
781 
782 int
fpos_ensure_selected(view_t * view,const char name[])783 fpos_ensure_selected(view_t *view, const char name[])
784 {
785 	int file_pos;
786 	char nm[NAME_MAX + 1];
787 
788 	/* Don't reset filters to find "file with empty name". */
789 	if(name[0] == '\0')
790 	{
791 		return 0;
792 	}
793 
794 	/* This is for compatibility with paths loaded from vifminfo that have
795 	 * trailing slash. */
796 	copy_str(nm, sizeof(nm), name);
797 	chosp(nm);
798 
799 	file_pos = fpos_find_by_name(view, nm);
800 	if(file_pos < 0 && file_can_be_displayed(view->curr_dir, nm))
801 	{
802 		if(nm[0] == '.')
803 		{
804 			dot_filter_set(view, 1);
805 			file_pos = fpos_find_by_name(view, nm);
806 		}
807 
808 		if(file_pos < 0)
809 		{
810 			name_filters_remove(view);
811 
812 			/* name_filters_remove() postpones reloading of list files. */
813 			(void)populate_dir_list(view, 1);
814 
815 			file_pos = fpos_find_by_name(view, nm);
816 		}
817 	}
818 
819 	fpos_set_pos(view, (file_pos < 0) ? 0 : file_pos);
820 	return file_pos >= 0;
821 }
822 
823 /* Checks if file specified can be displayed.  Used to filter some files, that
824  * are hidden intentionally.  Returns non-zero if file can be made visible. */
825 static int
file_can_be_displayed(const char directory[],const char filename[])826 file_can_be_displayed(const char directory[], const char filename[])
827 {
828 	if(is_parent_dir(filename))
829 	{
830 		return cfg_parent_dir_is_visible(is_root_dir(directory));
831 	}
832 	return path_exists_at(directory, filename, DEREF);
833 }
834 
835 int
fpos_find_by_ch(const view_t * view,int ch,int backward,int wrap)836 fpos_find_by_ch(const view_t *view, int ch, int backward, int wrap)
837 {
838 	int x;
839 	const int upcase = (cfg.case_override & CO_GOTO_FILE)
840 	                 ? (cfg.case_ignore & CO_GOTO_FILE)
841 	                 : (cfg.ignore_case && !(cfg.smart_case && iswupper(ch)));
842 
843 	if(upcase)
844 	{
845 		ch = towupper(ch);
846 	}
847 
848 	x = view->list_pos;
849 	do
850 	{
851 		if(backward)
852 		{
853 			x--;
854 			if(x < 0)
855 			{
856 				if(wrap)
857 					x = view->list_rows - 1;
858 				else
859 					return -1;
860 			}
861 		}
862 		else
863 		{
864 			x++;
865 			if(x > view->list_rows - 1)
866 			{
867 				if(wrap)
868 					x = 0;
869 				else
870 					return -1;
871 			}
872 		}
873 
874 		if(ch > 255)
875 		{
876 			wchar_t wc = get_first_wchar(view->dir_entry[x].name);
877 			if(upcase)
878 				wc = towupper(wc);
879 			if(wc == (wchar_t)ch)
880 				break;
881 		}
882 		else
883 		{
884 			int c = view->dir_entry[x].name[0];
885 			if(upcase)
886 				c = towupper(c);
887 			if(c == ch)
888 				break;
889 		}
890 	}
891 	while(x != view->list_pos);
892 
893 	return x;
894 }
895 
896 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
897 /* vim: set cinoptions+=t0 : */
898