コード例 #1
0
int
paths_are_same(const char s[], const char t[])
{
	char s_real[PATH_MAX];
	char t_real[PATH_MAX];

	if(os_realpath(s, s_real) != s_real || os_realpath(t, t_real) != t_real)
	{
		return (stroscmp(s, t) == 0);
	}
	return (stroscmp(s_real, t_real) == 0);
}
コード例 #2
0
ファイル: sort.c プロジェクト: acklinr/vifm
/* Compares two file names according to symbolic link target.  Returns standard
 * -1, 0, 1 for comparisons. */
static int
compare_targets(const dir_entry_t *f, const dir_entry_t *s)
{
	char full_path[PATH_MAX + 1];
	char nlink[PATH_MAX + 1], plink[PATH_MAX + 1];

	if((f->type == FT_LINK) != (s->type == FT_LINK))
	{
		/* One of the entries is not a link. */
		return (f->type == FT_LINK) ? 1 : -1;
	}
	if(f->type != FT_LINK)
	{
		/* Both entries are not symbolic links. */
		return 0;
	}

	/* Both entries are symbolic links. */

	get_full_path_of(f, sizeof(full_path), full_path);
	if(get_link_target(full_path, nlink, sizeof(nlink)) != 0)
	{
		return 0;
	}
	get_full_path_of(s, sizeof(full_path), full_path);
	if(get_link_target(full_path, plink, sizeof(plink)) != 0)
	{
		return 0;
	}

	return stroscmp(nlink, plink);
}
コード例 #3
0
ファイル: colorscheme_menu.c プロジェクト: cfillion/vifm
/* Sorting function for qsort(). */
static int
sorter(const void *first, const void *second)
{
	const char *stra = *(const char **)first;
	const char *strb = *(const char **)second;
	return stroscmp(stra, strb);
}
コード例 #4
0
ファイル: dirhistory_menu.c プロジェクト: francogonzaga/vifm
int
show_history_menu(FileView *view)
{
	int i;
	static menu_info m;

	init_menu_info(&m, DIRHISTORY, strdup("History disabled or empty"));

	m.title = strdup(" Directory History ");

	for(i = 0; i < view->history_num && i < cfg.history_len; i++)
	{
		int j;
		if(view->history[i].dir[0] == '\0')
			break;
		for(j = i + 1; j < view->history_num && j < cfg.history_len; j++)
			if(stroscmp(view->history[i].dir, view->history[j].dir) == 0)
				break;
		if(j < view->history_num && j < cfg.history_len)
			continue;
		if(!is_valid_dir(view->history[i].dir))
			continue;

		/* Change the current dir to reflect the current file. */
		if(stroscmp(view->history[i].dir, view->curr_dir) == 0)
		{
			(void)replace_string(&view->history[i].file,
					view->dir_entry[view->list_pos].name);
			m.pos = m.len;
		}

		m.len = add_to_string_array(&m.items, m.len, 1, view->history[i].dir);
	}

	/* Reverse order in which items appear. */
	for(i = 0; i < m.len/2; i++)
	{
		char *t = m.items[i];
		m.items[i] = m.items[m.len - 1 - i];
		m.items[m.len - 1 - i] = t;
	}
	m.pos = m.len - 1 - m.pos;

	return display_menu(&m, view);
}
コード例 #5
0
int
flist_find_entry(const FileView *view, const char file[], const char dir[])
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		if(dir != NULL && stroscmp(view->dir_entry[i].origin, dir) != 0)
		{
			continue;
		}

		if(stroscmp(view->dir_entry[i].name, file) == 0)
		{
			return i;
		}
	}
	return -1;
}
コード例 #6
0
ファイル: trash.c プロジェクト: cosminadrianpopescu/vifm
int
is_in_trash(const char trash_name[])
{
	int i;
	for(i = 0; i < nentries; i++)
	{
		if(stroscmp(trash_list[i].trash_name, trash_name) == 0)
			return 1;
	}
	return 0;
}
コード例 #7
0
ファイル: variables.c プロジェクト: ackeack/workenv
/* searches for existent variable */
static envvar_t *
find_record(const char *name)
{
	int i;
	for(i = 0; i < nvars; i++)
	{
		if(vars[i].name != NULL && stroscmp(vars[i].name, name) == 0)
			return &vars[i];
	}
	return NULL;
}
コード例 #8
0
ファイル: path.c プロジェクト: jubalh/vifm
int
paths_are_equal(const char s[], const char t[])
{
	/* Some additional space is allocated for adding slashes. */
	char s_can[strlen(s) + 8];
	char t_can[strlen(t) + 8];

	canonicalize_path(s, s_can, sizeof(s_can));
	canonicalize_path(t, t_can, sizeof(t_can));

	return stroscmp(s_can, t_can) == 0;
}
コード例 #9
0
ファイル: commands_completion.c プロジェクト: sshilovsky/vifm
char *
fast_run_complete(const char cmd[])
{
	char *result = NULL;
	const char *args;
	char command[NAME_MAX];
	char *completed;

	args = extract_cmd_name(cmd, 0, sizeof(command), command);

	if(is_path_absolute(command))
	{
		return strdup(cmd);
	}

	vle_compl_reset();
	complete_command_name(command);
	vle_compl_unite_groups();
	completed = vle_compl_next();

	if(vle_compl_get_count() > 2)
	{
		int c = vle_compl_get_count() - 1;
		while(c-- > 0)
		{
			if(stroscmp(command, completed) == 0)
			{
				result = strdup(cmd);
				break;
			}
			else
			{
				free(completed);
				completed = vle_compl_next();
			}
		}

		if(result == NULL)
		{
			status_bar_error("Command beginning is ambiguous");
		}
	}
	else
	{
		free(completed);
		completed = vle_compl_next();
		result = format_str("%s %s", completed, args);
	}
	free(completed);

	return result;
}
コード例 #10
0
ファイル: utils.c プロジェクト: jubalh/vifm
int
vifm_chdir(const char path[])
{
	char curr_path[PATH_MAX];
	if(getcwd(curr_path, sizeof(curr_path)) == curr_path)
	{
		if(stroscmp(curr_path, path) == 0)
		{
			return 0;
		}
	}
	return os_chdir(path);
}
コード例 #11
0
ファイル: registers.c プロジェクト: acklinr/vifm
static int
check_for_duplicate_file_names(reg_t *reg, const char file[])
{
	int i;
	for(i = 0; i < reg->nfiles; ++i)
	{
		if(stroscmp(file, reg->files[i]) == 0)
		{
			return 1;
		}
	}
	return 0;
}
コード例 #12
0
ファイル: bookmarks.c プロジェクト: lyuts/vifm
int
check_mark_directory(FileView *view, char mark)
{
	const bookmark_t *const bmark = get_bookmark(mark);

	if(!is_bmark_empty(bmark))
	{
		if(stroscmp(view->curr_dir, bmark->directory) == 0)
		{
			return find_file_pos_in_list(view, bmark->file);
		}
	}

	return -1;
}
コード例 #13
0
ファイル: path_env.c プロジェクト: serjepatoff/vifm
/* Checks if PATH environment variable was changed. Returns non-zero if path was
 * altered since last call. */
static int
path_env_was_changed(int force)
{
	const char *path;

	path = local_getenv("PATH");

	if(clean_path != NULL && stroscmp(clean_path, path) == 0)
	{
		return force;
	}

	(void)replace_string(&clean_path, path);
	return 1;
}
コード例 #14
0
ファイル: path.c プロジェクト: jubalh/vifm
int
is_root_dir(const char *path)
{
#ifdef _WIN32
	if(isalpha(path[0]) && stroscmp(path + 1, ":/") == 0)
		return 1;

	if(path[0] == '/' && path[1] == '/' && path[2] != '\0')
	{
		char *p = strchr(path + 2, '/');
		if(p == NULL || p[1] == '\0')
			return 1;
	}
#endif
	return (path[0] == '/' && path[1] == '\0');
}
コード例 #15
0
ファイル: bmarks.c プロジェクト: acklinr/vifm
int
bmark_is_older(const char path[], time_t than)
{
	size_t i;
	char canonic_path[strlen(path) + 16U];
	make_canonic(path, canonic_path, sizeof(canonic_path));

	for(i = 0U; i < bmark_count; ++i)
	{
		if(stroscmp(canonic_path, bmarks[i].path) == 0)
		{
			return bmarks[i].timestamp < than;
		}
	}

	return 1;
}
コード例 #16
0
ファイル: bmarks.c プロジェクト: acklinr/vifm
void
bmarks_file_moved(const char src[], const char dst[])
{
	size_t i;
	char canonic_src[strlen(src) + 16U], canonic_dst[strlen(dst) + 16U];
	make_canonic(src, canonic_src, sizeof(canonic_src));
	make_canonic(dst, canonic_dst, sizeof(canonic_dst));

	/* Renames bookmark. */
	for(i = 0U; i < bmark_count; ++i)
	{
		if(stroscmp(canonic_src, bmarks[i].path) == 0)
		{
			(void)replace_string(&bmarks[i].path, canonic_dst);
			break;
		}
	}
}
コード例 #17
0
ファイル: path.c プロジェクト: jubalh/vifm
char *
cut_extension(char path[])
{
	char *e;
	char *ext;

	if((ext = strrchr(path, '.')) == NULL)
		return path + strlen(path);

	*ext = '\0';
	if((e = strrchr(path, '.')) != NULL && stroscmp(e + 1, "tar") == 0)
	{
		*ext = '.';
		ext = e;
	}
	*ext = '\0';

	return ext + 1;
}
コード例 #18
0
ファイル: bmarks.c プロジェクト: acklinr/vifm
/* Changes value of existing bookmark.  When value was found *ret is set to
 * error code.  Returns non-zero if value was found and zero otherwise. */
static int
change_bmark(const char path[], const char tags[], time_t timestamp, int *ret)
{
	size_t i;
	char canonic_path[strlen(path) + 16U];
	make_canonic(path, canonic_path, sizeof(canonic_path));

	/* Try to update tags of an existing bookmark. */
	for(i = 0U; i < bmark_count; ++i)
	{
		if(stroscmp(canonic_path, bmarks[i].path) == 0)
		{
			*ret = replace_string(&bmarks[i].tags, tags);
			if(*ret == 0)
			{
				bmarks[i].timestamp = timestamp;
			}
			return 0;
		}
	}

	return 1;
}
コード例 #19
0
ファイル: view.c プロジェクト: cosminadrianpopescu/vifm
/* Either makes use of abandoned view or prunes it.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
try_ressurect_abandoned(const char full_path[], int explore)
{
	const int same_file = vi->abandoned
	                   && vi->view == (explore ? curr_view : other_view)
	                   && vi->filename != NULL
	                   && stroscmp(vi->filename, full_path) == 0;
	if(!same_file)
	{
		return 1;
	}

	if(explore)
	{
		vi->view->explore_mode = 0;
		reset_view_info(vi);
		return 1;
	}
	else
	{
		vle_mode_set(VIEW_MODE, VMT_SECONDARY);
		return 0;
	}
}
コード例 #20
0
void
vifm_restart(void)
{
	FileView *tmp_view;

	curr_stats.restart_in_progress = 1;

	/* All user mappings in all modes. */
	vle_keys_user_clear();

	/* User defined commands. */
	execute_cmd("comclear");

	/* Autocommands. */
	vle_aucmd_remove(NULL, NULL);

	/* Directory histories. */
	ui_view_clear_history(&lwin);
	ui_view_clear_history(&rwin);

	/* All kinds of history. */
	(void)hist_reset(&cfg.search_hist, cfg.history_len);
	(void)hist_reset(&cfg.cmd_hist, cfg.history_len);
	(void)hist_reset(&cfg.prompt_hist, cfg.history_len);
	(void)hist_reset(&cfg.filter_hist, cfg.history_len);
	cfg.history_len = 0;

	/* Session status.  Must be reset _before_ options, because options take some
	 * of values from status. */
	(void)reset_status(&cfg);

	/* Options of current pane. */
	reset_options_to_default();
	/* Options of other pane. */
	tmp_view = curr_view;
	curr_view = other_view;
	load_view_options(other_view);
	reset_options_to_default();
	curr_view = tmp_view;

	/* File types and viewers. */
	ft_reset(curr_stats.exec_env_type == EET_EMULATOR_WITH_X);

	/* Undo list. */
	reset_undo_list();

	/* Directory stack. */
	dir_stack_clear();

	/* Registers. */
	regs_reset();

	/* Clear all marks and bookmarks. */
	clear_all_marks();
	bmarks_clear();

	/* Reset variables. */
	clear_envvars();
	init_variables();
	/* This update is needed as clear_variables() will reset $PATH. */
	update_path_env(1);

	reset_views();
	read_info_file(1);
	flist_hist_save(&lwin, NULL, NULL, -1);
	flist_hist_save(&rwin, NULL, NULL, -1);

	/* Color schemes. */
	if(stroscmp(curr_stats.color_scheme, DEF_CS_NAME) != 0 &&
			cs_exists(curr_stats.color_scheme))
	{
		if(cs_load_primary(curr_stats.color_scheme) != 0)
		{
			cs_load_defaults();
		}
	}
	else
	{
		cs_load_defaults();
	}
	cs_load_pairs();

	cfg_load();
	exec_startup_commands(&vifm_args);

	curr_stats.restart_in_progress = 0;

	/* Trigger auto-commands for initial directories. */
	vle_aucmd_execute("DirEnter", lwin.curr_dir, &lwin);
	vle_aucmd_execute("DirEnter", rwin.curr_dir, &rwin);

	update_screen(UT_REDRAW);
}
コード例 #21
0
ファイル: path_env.c プロジェクト: serjepatoff/vifm
/* Breaks PATH environment variable into list of paths. */
static void
split_path_list(void)
{
	const char *path, *p, *q;
	int i;

	path = env_get("PATH");

	if(paths != NULL)
		free_string_array(paths, paths_count);

	paths_count = 1;
	p = path;
	while((p = strchr(p, ':')) != NULL)
	{
		paths_count++;
		p++;
	}

	paths = reallocarray(NULL, paths_count, sizeof(paths[0]));
	if(paths == NULL)
	{
		paths_count = 0;
		return;
	}

	i = 0;
	p = path - 1;
	do
	{
		int j;
		char *s;

		p++;
#ifndef _WIN32
		q = strchr(p, ':');
#else
		q = strchr(p, ';');
#endif
		if(q == NULL)
		{
			q = p + strlen(p);
		}

		s = malloc(q - p + 1U);
		if(s == NULL)
		{
			free_string_array(paths, i - 1);
			paths = NULL;
			paths_count = 0;
			return;
		}
		copy_str(s, q - p + 1U, p);

		p = q;

		s = replace_tilde(s);

		/* No need to check "." path for existence. */
		if(strcmp(s, ".") != 0)
		{
			if(!path_exists(s, DEREF))
			{
				free(s);
				continue;
			}
		}

		paths[i++] = s;

		for(j = 0; j < i - 1; j++)
		{
			if(stroscmp(paths[j], s) == 0)
			{
				free(s);
				i--;
				break;
			}
		}
	}
	while(q[0] != '\0');
	paths_count = i;
}
コード例 #22
0
ファイル: commands_completion.c プロジェクト: sshilovsky/vifm
/*
 * type: CT_*
 */
void
filename_completion(const char *str, CompletionType type)
{
	/* TODO refactor filename_completion(...) function */
	DIR * dir;
	char * dirname;
	char * filename;
	char * temp;

	if(str[0] == '~' && strchr(str, '/') == NULL)
	{
		char *const tilde_expanded = expand_tilde(str);
		vle_compl_add_path_match(tilde_expanded);
		free(tilde_expanded);
		return;
	}

	dirname = expand_tilde(str);
	filename = strdup(dirname);

	temp = cmds_expand_envvars(dirname);
	free(dirname);
	dirname = temp;

	temp = strrchr(dirname, '/');
	if(temp != NULL && type != CT_FILE && type != CT_FILE_WOE)
	{
		strcpy(filename, ++temp);
		*temp = '\0';
	}
	else
	{
		dirname = realloc(dirname, 2);
		strcpy(dirname, ".");
	}

#ifdef _WIN32
	if(is_unc_root(dirname) ||
			(stroscmp(dirname, ".") == 0 && is_unc_root(curr_view->curr_dir)) ||
			(stroscmp(dirname, "/") == 0 && is_unc_path(curr_view->curr_dir)))
	{
		char buf[PATH_MAX];
		if(!is_unc_root(dirname))
			snprintf(buf,
					strchr(curr_view->curr_dir + 2, '/') - curr_view->curr_dir + 1, "%s",
					curr_view->curr_dir);
		else
			snprintf(buf, sizeof(buf), "%s", dirname);

		complete_with_shared(buf, filename);
		free(filename);
		free(dirname);
		return;
	}
	if(is_unc_path(curr_view->curr_dir))
	{
		char buf[PATH_MAX];
		if(is_path_absolute(dirname) && !is_unc_root(curr_view->curr_dir))
			snprintf(buf,
					strchr(curr_view->curr_dir + 2, '/') - curr_view->curr_dir + 2, "%s",
					curr_view->curr_dir);
		else
			snprintf(buf, sizeof(buf), "%s", curr_view->curr_dir);
		strcat(buf, dirname);
		chosp(buf);
		(void)replace_string(&dirname, buf);
	}
#endif

	dir = opendir(dirname);

	if(dir == NULL || vifm_chdir(dirname) != 0)
	{
		vle_compl_add_path_match(filename);
	}
	else
	{
		filename_completion_internal(dir, dirname, filename, type);
		(void)vifm_chdir(curr_view->curr_dir);
	}

	free(filename);
	free(dirname);

	if(dir != NULL)
	{
		closedir(dir);
	}
}
コード例 #23
0
ファイル: vifm.c プロジェクト: phantasea/vifm
void
vifm_restart(void)
{
	view_t *tmp_view;

	curr_stats.restart_in_progress = 1;

	/* All user mappings in all modes. */
	vle_keys_user_clear();

	/* User defined commands. */
	vle_cmds_run("comclear");

	/* Autocommands. */
	vle_aucmd_remove(NULL, NULL);

	/* All kinds of histories. */
	cfg_resize_histories(0);

	/* Session status.  Must be reset _before_ options, because options take some
	 * of values from status. */
	(void)stats_reset(&cfg);

	/* Options of current pane. */
	vle_opts_restore_defaults();
	/* Options of other pane. */
	tmp_view = curr_view;
	curr_view = other_view;
	load_view_options(other_view);
	vle_opts_restore_defaults();
	curr_view = tmp_view;

	/* File types and viewers. */
	ft_reset(curr_stats.exec_env_type == EET_EMULATOR_WITH_X);

	/* Undo list. */
	un_reset();

	/* Directory stack. */
	dir_stack_clear();

	/* Registers. */
	regs_reset();

	/* Clear all marks and bookmarks. */
	clear_all_marks();
	bmarks_clear();

	/* Reset variables. */
	clear_envvars();
	init_variables();
	/* This update is needed as clear_variables() will reset $PATH. */
	update_path_env(1);

	reset_views();
	read_info_file(1);
	flist_hist_save(&lwin, NULL, NULL, -1);
	flist_hist_save(&rwin, NULL, NULL, -1);

	/* Color schemes. */
	if(stroscmp(curr_stats.color_scheme, DEF_CS_NAME) != 0 &&
			cs_exists(curr_stats.color_scheme))
	{
		cs_load_primary(curr_stats.color_scheme);
	}
	else
	{
		cs_load_defaults();
	}
	cs_load_pairs();

	cfg_load();

	/* Reloading of tabs needs to happen after configuration is read so that new
	 * values from lwin and rwin got propagated. */
	tabs_reload();

	exec_startup_commands(&vifm_args);

	curr_stats.restart_in_progress = 0;

	/* Trigger auto-commands for initial directories. */
	(void)vifm_chdir(flist_get_dir(&lwin));
	vle_aucmd_execute("DirEnter", flist_get_dir(&lwin), &lwin);
	(void)vifm_chdir(flist_get_dir(&rwin));
	vle_aucmd_execute("DirEnter", flist_get_dir(&rwin), &rwin);

	update_screen(UT_REDRAW);
}
コード例 #24
0
/* Composes side-by-side comparison of files in two views. */
static void
fill_side_by_side(entries_t curr, entries_t other, int group_paths)
{
	enum { UP, LEFT, DIAG };

	int i, j;
	/* Describes results of solving sub-problems. */
	int (*d)[other.nentries + 1] =
		reallocarray(NULL, curr.nentries + 1, sizeof(*d));
	/* Describes paths (backtracking handles ambiguity badly). */
	char (*p)[other.nentries + 1] =
		reallocarray(NULL, curr.nentries + 1, sizeof(*p));

	for(i = 0; i <= curr.nentries; ++i)
	{
		for(j = 0; j <= other.nentries; ++j)
		{
			if(i == 0)
			{
				d[i][j] = j;
				p[i][j] = LEFT;
			}
			else if(j == 0)
			{
				d[i][j] = i;
				p[i][j] = UP;
			}
			else
			{
				const dir_entry_t *centry = &curr.entries[curr.nentries - i];
				const dir_entry_t *oentry = &other.entries[other.nentries - j];

				d[i][j] = MIN(d[i - 1][j] + 1, d[i][j - 1] + 1);
				p[i][j] = d[i][j] == d[i - 1][j] + 1 ? UP : LEFT;

				if((centry->id == oentry->id ||
							(group_paths && stroscmp(centry->name, oentry->name) == 0)) &&
						d[i - 1][j - 1] <= d[i][j])
				{
					d[i][j] = d[i - 1][j - 1];
					p[i][j] = DIAG;
				}
			}
		}
	}

	i = curr.nentries;
	j = other.nentries;
	while(i != 0 || j != 0)
	{
		switch(p[i][j])
		{
			dir_entry_t *e;

			case UP:
				e = &curr.entries[curr.nentries - 1 - --i];
				flist_custom_put(curr_view, e);
				flist_custom_add_separator(other_view, e->id);
				break;
			case LEFT:
				e = &other.entries[other.nentries - 1 - --j];
				flist_custom_put(other_view, e);
				flist_custom_add_separator(curr_view, e->id);
				break;
			case DIAG:
				flist_custom_put(curr_view, &curr.entries[curr.nentries - 1 - --i]);
				flist_custom_put(other_view, &other.entries[other.nentries - 1 - --j]);
				break;
		}
	}

	free(d);
	free(p);

	/* Entries' data has been moved out of them, so need to free only the
	 * lists. */
	dynarray_free(curr.entries);
	dynarray_free(other.entries);
}
コード例 #25
0
ファイル: find_program.c プロジェクト: KryDos/vifm
static int prog_exists(const char *name)
{
	return stroscmp(name, "console") == 0;
}
コード例 #26
0
int
flist_find_group(const FileView *view, int next)
{
	/* TODO: refactor/simplify this function (flist_find_group()). */

	const int correction = next ? -1 : 0;
	const int lb = correction;
	const int ub = view->list_rows + correction;
	const int inc = next ? +1 : -1;

	int pos = view->list_pos;
	dir_entry_t *pentry = &view->dir_entry[pos];
	const char *ext = get_last_ext(pentry->name);
	size_t char_width = utf8_chrw(pentry->name);
	wchar_t ch = towupper(get_first_wchar(pentry->name));
	const SortingKey sorting_key =
		flist_custom_active(view) && cv_compare(view->custom.type)
		? SK_BY_ID
		: abs(view->sort[0]);
	const int is_dir = fentry_is_dir(pentry);
	const char *const type_str = get_type_str(pentry->type);
	regmatch_t pmatch = { .rm_so = 0, .rm_eo = 0 };
#ifndef _WIN32
	char perms[16];
	get_perm_string(perms, sizeof(perms), pentry->mode);
#endif
	if(sorting_key == SK_BY_GROUPS)
	{
		pmatch = get_group_match(&view->primary_group, pentry->name);
	}
	while(pos > lb && pos < ub)
	{
		dir_entry_t *nentry;
		pos += inc;
		nentry = &view->dir_entry[pos];
		switch(sorting_key)
		{
			case SK_BY_FILEEXT:
				if(fentry_is_dir(nentry))
				{
					if(strncmp(pentry->name, nentry->name, char_width) != 0)
					{
						return pos;
					}
				}
				if(strcmp(get_last_ext(nentry->name), ext) != 0)
				{
					return pos;
				}
				break;
			case SK_BY_EXTENSION:
				if(strcmp(get_last_ext(nentry->name), ext) != 0)
					return pos;
				break;
			case SK_BY_GROUPS:
				{
					regmatch_t nmatch = get_group_match(&view->primary_group,
							nentry->name);

					if(pmatch.rm_eo - pmatch.rm_so != nmatch.rm_eo - nmatch.rm_so ||
							(pmatch.rm_eo != pmatch.rm_so &&
							 strncmp(pentry->name + pmatch.rm_so, nentry->name + nmatch.rm_so,
								 pmatch.rm_eo - pmatch.rm_so + 1U) != 0))
						return pos;
				}
				break;
			case SK_BY_TARGET:
				if((nentry->type == FT_LINK) != (pentry->type == FT_LINK))
				{
					/* One of the entries is not a link. */
					return pos;
				}
				if(nentry->type == FT_LINK)
				{
					/* Both entries are symbolic links. */
					char full_path[PATH_MAX];
					char nlink[PATH_MAX], plink[PATH_MAX];

					get_full_path_of(nentry, sizeof(full_path), full_path);
					if(get_link_target(full_path, nlink, sizeof(nlink)) != 0)
					{
						return pos;
					}
					get_full_path_of(pentry, sizeof(full_path), full_path);
					if(get_link_target(full_path, plink, sizeof(plink)) != 0)
					{
						return pos;
					}

					if(stroscmp(nlink, plink) != 0)
					{
						return pos;
					}
				}
				break;
			case SK_BY_NAME:
				if(strncmp(pentry->name, nentry->name, char_width) != 0)
					return pos;
				break;
			case SK_BY_INAME:
				if((wchar_t)towupper(get_first_wchar(nentry->name)) != ch)
					return pos;
				break;
			case SK_BY_SIZE:
				if(nentry->size != pentry->size)
					return pos;
				break;
			case SK_BY_NITEMS:
				if(entry_get_nitems(view, nentry) != entry_get_nitems(view, pentry))
					return pos;
				break;
			case SK_BY_TIME_ACCESSED:
				if(nentry->atime != pentry->atime)
					return pos;
				break;
			case SK_BY_TIME_CHANGED:
				if(nentry->ctime != pentry->ctime)
					return pos;
				break;
			case SK_BY_TIME_MODIFIED:
				if(nentry->mtime != pentry->mtime)
					return pos;
				break;
			case SK_BY_DIR:
				if(is_dir != fentry_is_dir(nentry))
				{
					return pos;
				}
				break;
			case SK_BY_TYPE:
				if(get_type_str(nentry->type) != type_str)
				{
					return pos;
				}
				break;
#ifndef _WIN32
			case SK_BY_GROUP_NAME:
			case SK_BY_GROUP_ID:
				if(nentry->gid != pentry->gid)
					return pos;
				break;
			case SK_BY_OWNER_NAME:
			case SK_BY_OWNER_ID:
				if(nentry->uid != pentry->uid)
					return pos;
				break;
			case SK_BY_MODE:
				if(nentry->mode != pentry->mode)
					return pos;
				break;
			case SK_BY_PERMISSIONS:
				{
					char nperms[16];
					get_perm_string(nperms, sizeof(nperms), nentry->mode);
					if(strcmp(nperms, perms) != 0)
					{
						return pos;
					}
					break;
				}
			case SK_BY_NLINKS:
				if(nentry->nlinks != pentry->nlinks)
				{
					return pos;
				}
				break;
#endif
		}
		/* Id sorting is builtin only and is defined outside SortingKey
		 * enumeration. */
		if((int)sorting_key == SK_BY_ID)
		{
			if(nentry->id != pentry->id)
			{
				return pos;
			}
		}
	}
	return pos;
}

/* Finds pointer to the beginning of the last extension of the file name.
 * Returns the pointer, which might point to the NUL byte if there are no
 * extensions. */
static const char *
get_last_ext(const char name[])
{
	const char *const ext = strrchr(name, '.');
	return (ext == NULL) ? (name + strlen(name)) : (ext + 1);
}

int
flist_find_dir_group(const FileView *view, int next)
{
	const int correction = next ? -1 : 0;
	const int lb = correction;
	const int ub = view->list_rows + correction;
	const int inc = next ? +1 : -1;

	int pos = curr_view->list_pos;
	dir_entry_t *pentry = &curr_view->dir_entry[pos];
	const int is_dir = fentry_is_dir(pentry);
	while(pos > lb && pos < ub)
	{
		dir_entry_t *nentry;
		pos += inc;
		nentry = &curr_view->dir_entry[pos];
		if(is_dir != fentry_is_dir(nentry))
		{
			break;
		}
	}
	return pos;
}

int
flist_first_sibling(const FileView *view)
{
	const int parent = view->list_pos - view->dir_entry[view->list_pos].child_pos;
	return (parent == view->list_pos ? 0 : parent + 1);
}

int
flist_last_sibling(const FileView *view)
{
	int pos = view->list_pos - view->dir_entry[view->list_pos].child_pos;
	if(pos == view->list_pos)
	{
		/* For top-level entry, find the last top-level entry. */
		pos = view->list_rows - 1;
		while(view->dir_entry[pos].child_pos != 0)
		{
			pos -= view->dir_entry[pos].child_pos;
		}
	}
	else
	{
		/* For non-top-level entry, go to last tree item and go up until our
		 * child. */
		const int parent = pos;
		pos = parent + view->dir_entry[parent].child_count;
		while(pos - view->dir_entry[pos].child_pos != parent)
		{
			pos -= view->dir_entry[pos].child_pos;
		}
	}
	return pos;
}