/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#define HOME_EV "HOME"
#define VIFM_EV "VIFM"
#define MYVIFMRC_EV "MYVIFMRC"
#define TRASH "Trash"
#define LOG "log"
#define VIFMRC "vifmrc"

#ifndef __APPLE__
#define SAMPLE_VIFMRC "vifmrc"
#define SAMPLE_MEDIAPRG "vifm-media"
#else
#define SAMPLE_VIFMRC "vifmrc-osx"
#define SAMPLE_MEDIAPRG "vifm-media-osx"
#endif

#include <assert.h> /* assert() */
#include <limits.h> /* INT_MIN */
#include <stddef.h> /* NULL size_t */
#include <stdio.h> /* FILE snprintf() */
#include <stdlib.h> /* free() */
#include <string.h> /* memmove() memset() strdup() */

#include "../compat/fs_limits.h"
#include "../compat/os.h"
#include "../int/term_title.h"
#include "../io/iop.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../ui/color_scheme.h"
#include "../ui/statusbar.h"
#include "../ui/tabs.h"
#include "../ui/ui.h"
#include "../utils/env.h"
#include "../utils/file_streams.h"
#include "../utils/fs.h"
#include "../utils/log.h"
#include "../utils/macros.h"
#include "../utils/str.h"
#include "../utils/path.h"
#include "../utils/string_array.h"
#include "../utils/utils.h"
#include "../cmd_core.h"
#include "../filelist.h"
#include "../flist_hist.h"
#include "../marks.h"
#include "../opt_handlers.h"
#include "../status.h"
#include "../types.h"
#include "../vifm.h"

/* Maximum supported by the implementation length of line in vifmrc file. */
#define MAX_VIFMRC_LINE_LEN 4*1024

/* Default value of shell command used if $SHELL environment variable isn't
 * set. */
#ifndef _WIN32
#define DEFAULT_SHELL_CMD "/bin/sh"
#else
#define DEFAULT_SHELL_CMD "cmd"
#endif

/* Default value of the cd path list. */
#define DEFAULT_CD_PATH ""

config_t cfg;

static void find_home_dir(void);
static int try_home_envvar_for_home(void);
static int try_userprofile_envvar_for_home(void);
static int try_homepath_envvar_for_home(void);
static void find_config_dir(void);
static int try_vifm_envvar_for_conf(void);
static int try_exe_directory_for_conf(void);
static int try_home_envvar_for_conf(int force);
static int try_appdata_for_conf(void);
static int try_xdg_for_conf(void);
static void find_data_dir(char buf[], size_t buf_size);
static void find_config_file(void);
static int try_myvifmrc_envvar_for_vifmrc(void);
static int try_exe_directory_for_vifmrc(void);
static int try_vifm_vifmrc_for_vifmrc(void);
static void store_config_paths(const char data_dir[]);
static void setup_dirs(void);
static void create_scripts_dir(void);
static void copy_rc_file(void);
static void add_default_marks(void);
static int source_file_internal(strlist_t lines, const char filename[]);
static void show_sourcing_error(const char filename[], int line_num);

void
cfg_init(void)
{
	cfg.session = NULL;

	cfg.history_len = 15;

	cfg.auto_execute = 0;
	cfg.time_format = strdup("%m/%d %H:%M");
	cfg.wrap_quick_view = 1;
	cfg.undo_levels = 100;
	cfg.sort_numbers = 0;
	cfg.follow_links = 1;
	cfg.fast_run = 0;
	cfg.confirm = CONFIRM_DELETE | CONFIRM_PERM_DELETE;
	cfg.vi_command = strdup("vim");
	cfg.vi_cmd_bg = 0;
	cfg.vi_x_command = strdup("");
	cfg.vi_x_cmd_bg = 0;
	cfg.use_trash = 1;
	cfg.use_term_multiplexer = 0;
	cfg.use_vim_help = 0;

	cfg.wild_menu = 0;
	cfg.wild_popup = 0;

	char *error;
	cfg.wild_inc = matcher_alloc_glob("", &error);
	free(error);

	cfg.sug.flags = 0;
	cfg.sug.maxregfiles = 5;
	cfg.sug.delay = 500;

	cfg.ignore_case = 0;
	cfg.smart_case = 0;
	cfg.hl_search = 1;
	cfg.vifm_info = VINFO_MARKS | VINFO_BOOKMARKS;
	cfg.session_options = VINFO_TUI | VINFO_STATE | VINFO_TABS | VINFO_SAVEDIRS
	                    | VINFO_DHISTORY;
	cfg.scroll_off = 0;
	cfg.gdefault = 0;
	cfg.scroll_bind = 0;
	cfg.wrap_scan = 1;
	cfg.inc_search = 0;
	cfg.selection_is_primary = 1;
	cfg.tab_switches_pane = 1;
	cfg.use_system_calls = 0;
	cfg.tab_stop = 8;
	cfg.ruler_format = strdup("%l/%S ");
	cfg.status_line = strdup("");
	cfg.tab_line = strdup("");

	cfg.lines = INT_MIN;
	cfg.columns = INT_MIN;

	cfg.dot_dirs = DD_NONROOT_PARENT | DD_TREE_LEAFS_PARENT;

	cfg.mouse = 0;

	cfg.filter_inverted_by_default = 1;

	cfg.apropos_prg = strdup("apropos %a");
	cfg.find_prg = strdup("find %s %a");
	cfg.grep_prg = strdup("grep -n -H -I -r %i %a %s");
	cfg.locate_prg = strdup("locate %a");
	cfg.delete_prg = strdup("");
	cfg.media_prg = format_str("%s/" SAMPLE_MEDIAPRG, get_installed_data_dir());

	cfg.nav_open_files = 0;

	cfg.tail_tab_line_paths = 0;
	cfg.trunc_normal_sb_msgs = 0;
	cfg.shorten_title_paths = 1;
	cfg.short_term_mux_titles = 0;

	cfg.slow_fs_list = strdup("");

	cfg.cd_path = strdup(env_get_def("CDPATH", DEFAULT_CD_PATH));
	replace_char(cfg.cd_path, ':', ',');

	cfg.auto_cd = 0;

	cfg.ellipsis_position = 0;
	cfg.extra_padding = 1;
	cfg.side_borders_visible = 1;
	cfg.use_unicode_characters = 0;
	cfg.color_what = CW_ONE_ROW;
	cfg.flexible_splitter = 1;
	cfg.display_statusline = 1;

	cfg.vborder_filler = strdup(" ");
	cfg.hborder_filler = strdup("");
	cfg.millersep_filler = strdup("");

	cfg.set_title = term_title_restorable();

	cfg.always_show_io_details = 0;

	cfg.chase_links = 0;

	cfg.graphics_delay = 50000;
	cfg.hard_graphics_clear = 0;
	cfg.top_tree_stats = 0;
	cfg.max_tree_depth = 0;

	cfg.timeout_len = 1000;
	cfg.min_timeout_len = 150;

	/* Fill cfg.word_chars as if it was initialized from isspace() function. */
	memset(&cfg.word_chars, 1, sizeof(cfg.word_chars));
	cfg.word_chars['\x00'] = 0; cfg.word_chars['\x09'] = 0;
	cfg.word_chars['\x0a'] = 0; cfg.word_chars['\x0b'] = 0;
	cfg.word_chars['\x0c'] = 0; cfg.word_chars['\x0d'] = 0;
	cfg.word_chars['\x20'] = 0;

	cfg.view_dir_size = VDS_SIZE;

	cfg.log_file[0] = '\0';

	cfg_set_shell(env_get_def("SHELL", DEFAULT_SHELL_CMD));
	cfg.shell_cmd_flag = strdup((curr_stats.shell_type == ST_CMD) ? "/C" : "-c");

	memset(&cfg.type_decs, '\0', sizeof(cfg.type_decs));
	cfg.type_decs[FT_DIR][DECORATION_SUFFIX][0] = '/';

	cfg.name_decs = NULL;
	cfg.name_dec_count = 0;

	cfg.fast_file_cloning = 0;
	cfg.data_sync = 1;

	cfg.cvoptions = 0;

	cfg.case_override = 0;
	cfg.case_ignore = 0;

	cfg.sizefmt.base = 1024;
	cfg.sizefmt.precision = 0;
	cfg.sizefmt.ieci_prefixes = 0;
	cfg.sizefmt.space = 1;

	cfg.pane_tabs = 0;
	cfg.show_tab_line = STL_MULTIPLE;
	cfg.tab_prefix = strdup("[%N:");
	cfg.tab_label = strdup("");
	cfg.tab_suffix = strdup("]");

	cfg.auto_ch_pos = 1;
	cfg.ch_pos_on = CHPOS_STARTUP | CHPOS_DIRMARK | CHPOS_ENTER;
}

void
cfg_discover_paths(void)
{
	LOG_FUNC_ENTER;

	char data_dir[PATH_MAX + 1];

	find_home_dir();
	find_config_dir();
	find_data_dir(data_dir, sizeof(data_dir));
	find_config_file();

	store_config_paths(data_dir);

	setup_dirs();
}

/* Tries to find home directory. */
static void
find_home_dir(void)
{
	LOG_FUNC_ENTER;

	if(try_home_envvar_for_home()) return;
	if(try_userprofile_envvar_for_home()) return;
	if(try_homepath_envvar_for_home()) return;

	vifm_finish("Failed to find user's home directory.");
}

/* Tries to use HOME environment variable to find home directory.  Returns
 * non-zero on success, otherwise zero is returned. */
static int
try_home_envvar_for_home(void)
{
	LOG_FUNC_ENTER;

	const char *home = env_get(HOME_EV);
	return home != NULL && is_dir(home);
}

/* Tries to use USERPROFILE environment variable to find home directory.
 * Returns non-zero on success, otherwise zero is returned. */
static int
try_userprofile_envvar_for_home(void)
{
	LOG_FUNC_ENTER;

#ifndef _WIN32
	return 0;
#else
	char home[PATH_MAX + 1];
	const char *userprofile = env_get("USERPROFILE");
	if(userprofile == NULL || !is_dir(userprofile))
		return 0;
	copy_str(home, sizeof(home), userprofile);
	system_to_internal_slashes(home);
	env_set(HOME_EV, home);
	return 1;
#endif
}

/* Tries to use $HOMEDRIVE/$HOMEPATH as home directory.  Returns non-zero on
 * success, otherwise zero is returned. */
static int
try_homepath_envvar_for_home(void)
{
	LOG_FUNC_ENTER;

#ifndef _WIN32
	return 0;
#else
	char home[PATH_MAX + 1];
	const char *homedrive = env_get("HOMEDRIVE");
	const char *homepath = env_get("HOMEPATH");
	if(homedrive == NULL || !is_dir(homedrive))
		return 0;
	if(homepath == NULL || !is_dir(homepath))
		return 0;

	snprintf(home, sizeof(home), "%s%s", homedrive, homepath);
	system_to_internal_slashes(home);
	env_set(HOME_EV, home);
	return 1;
#endif
}

/* Tries to find configuration directory. */
static void
find_config_dir(void)
{
	LOG_FUNC_ENTER;

	if(try_vifm_envvar_for_conf()) return;
	if(try_exe_directory_for_conf()) return;
	if(try_home_envvar_for_conf(0)) return;
	if(try_appdata_for_conf()) return;
	if(try_xdg_for_conf()) return;

	if(!try_home_envvar_for_conf(1))
	{
		vifm_finish("Failed to determine location of configuration files.");
	}
}

/* Tries to use VIFM environment variable to find configuration directory.
 * Returns non-zero on success, otherwise zero is returned. */
static int
try_vifm_envvar_for_conf(void)
{
	LOG_FUNC_ENTER;

	const char *vifm = env_get(VIFM_EV);
	return vifm != NULL && is_dir(vifm);
}

/* Tries to use directory of executable file as configuration directory.
 * Returns non-zero on success, otherwise zero is returned. */
static int
try_exe_directory_for_conf(void)
{
	LOG_FUNC_ENTER;

	char exe_dir[PATH_MAX + 1];

	if(get_exe_dir(exe_dir, sizeof(exe_dir)) != 0)
	{
		return 0;
	}

	if(!path_exists_at(exe_dir, VIFMRC, DEREF))
	{
		return 0;
	}

	env_set(VIFM_EV, exe_dir);
	return 1;
}

/* Tries to use $HOME/.vifm as configuration directory.  Tries harder on force.
 * Returns non-zero on success, otherwise zero is returned. */
static int
try_home_envvar_for_conf(int force)
{
	LOG_FUNC_ENTER;

	char vifm[PATH_MAX + 1];

	const char *home = env_get(HOME_EV);
	if(home == NULL || !is_dir(home))
	{
		return 0;
	}

	snprintf(vifm, sizeof(vifm), "%s/.vifm", home);
	system_to_internal_slashes(vifm);

	if(!force && !is_dir(vifm))
	{
		return 0;
	}

	env_set(VIFM_EV, vifm);
	return 1;
}

/* Tries to use $APPDATA/Vifm as configuration directory.  Returns non-zero on
 * success, otherwise zero is returned. */
static int
try_appdata_for_conf(void)
{
	LOG_FUNC_ENTER;

#ifndef _WIN32
	return 0;
#else
	char vifm[PATH_MAX + 1];
	const char *appdata = env_get("APPDATA");
	if(appdata == NULL || !is_dir(appdata))
		return 0;
	snprintf(vifm, sizeof(vifm), "%s/Vifm", appdata);
	system_to_internal_slashes(vifm);
	env_set(VIFM_EV, vifm);
	return 1;
#endif
}

/* Tries to use $XDG_CONFIG_HOME/vifm as configuration directory.  Returns
 * non-zero on success, otherwise zero is returned. */
static int
try_xdg_for_conf(void)
{
	LOG_FUNC_ENTER;

	char *config_dir;

	const char *const config_home = env_get("XDG_CONFIG_HOME");
	if(!is_null_or_empty(config_home) && is_path_absolute(config_home))
	{
		config_dir = format_str("%s/vifm", config_home);
	}
	else if(path_exists_at(env_get(HOME_EV), ".config", DEREF))
	{
		config_dir = format_str("%s/.config/vifm", env_get(HOME_EV));
	}
	else
	{
		return 0;
	}

	env_set(VIFM_EV, config_dir);
	free(config_dir);

	/* If XDG is used for configuration directory, create correcponding data
	 * directory, so it's also used at the same time. */
	char data_dir[PATH_MAX + 1];
	find_data_dir(data_dir, sizeof(data_dir));
	(void)create_path(data_dir, S_IRWXU);

	return 1;
}

/* Tries to find XDG directory for storing data files. */
static void
find_data_dir(char buf[], size_t buf_size)
{
	LOG_FUNC_ENTER;

	assert(buf_size >= 4 && "Buffer for find_data_dir() is too small!");

	const char *const data_home = env_get("XDG_DATA_HOME");
	if(is_null_or_empty(data_home) || !is_path_absolute(data_home))
	{
		snprintf(buf, buf_size - 4, "%s/.local/share/", env_get(HOME_EV));
	}
	else
	{
		snprintf(buf, buf_size - 4, "%s/", data_home);
	}

	strcat(buf, "vifm");

	system_to_internal_slashes(buf);
}

/* Tries to find configuration file. */
static void
find_config_file(void)
{
	LOG_FUNC_ENTER;

	if(try_myvifmrc_envvar_for_vifmrc()) return;
	if(try_exe_directory_for_vifmrc()) return;
	if(try_vifm_vifmrc_for_vifmrc()) return;
}

/* Tries to use $MYVIFMRC as configuration file.  Returns non-zero on success,
 * otherwise zero is returned. */
static int
try_myvifmrc_envvar_for_vifmrc(void)
{
	LOG_FUNC_ENTER;

	const char *myvifmrc = env_get(MYVIFMRC_EV);
	return myvifmrc != NULL && path_exists(myvifmrc, DEREF);
}

/* Tries to use vifmrc in directory of executable file as configuration file.
 * Returns non-zero on success, otherwise zero is returned. */
static int
try_exe_directory_for_vifmrc(void)
{
	LOG_FUNC_ENTER;

	char exe_dir[PATH_MAX + 1];
	char vifmrc[PATH_MAX + 8];

	if(get_exe_dir(exe_dir, sizeof(exe_dir)) != 0)
	{
		return 0;
	}

	snprintf(vifmrc, sizeof(vifmrc), "%s/" VIFMRC, exe_dir);
	if(!path_exists(vifmrc, DEREF))
	{
		return 0;
	}

	env_set(MYVIFMRC_EV, vifmrc);
	return 1;
}

/* Tries to use $VIFM/vifmrc as configuration file.  Returns non-zero on
 * success, otherwise zero is returned. */
static int
try_vifm_vifmrc_for_vifmrc(void)
{
	LOG_FUNC_ENTER;

	char vifmrc[PATH_MAX + 1];
	const char *vifm = env_get(VIFM_EV);
	if(vifm == NULL || !is_dir(vifm))
		return 0;
	snprintf(vifmrc, sizeof(vifmrc), "%s/" VIFMRC, vifm);
	if(!path_exists(vifmrc, DEREF))
		return 0;
	env_set(MYVIFMRC_EV, vifmrc);
	return 1;
}

/* Writes path configuration file and directories for further usage. */
static void
store_config_paths(const char data_dir[])
{
	LOG_FUNC_ENTER;

	snprintf(cfg.home_dir, sizeof(cfg.home_dir), "%s/", env_get(HOME_EV));
	copy_str(cfg.config_dir, sizeof(cfg.config_dir), env_get(VIFM_EV));
	snprintf(cfg.colors_dir, sizeof(cfg.colors_dir), "%s/colors/",
			cfg.config_dir);

	const char *const trash_dir_fmt =
#ifndef _WIN32
			"%%r/.vifm-Trash-%%u,%s/" TRASH ",%%r/.vifm-Trash";
#else
			"%%r/.vifm-Trash,%s/" TRASH;
#endif

	/* Determine where to store data giving preference to directories that already
	 * exist. */
	const char *base;
	if(path_exists_at(data_dir, TRASH, DEREF))
	{
		base = data_dir;
	}
	else if(path_exists_at(cfg.config_dir, TRASH, DEREF))
	{
		base = cfg.config_dir;
	}
	else
	{
		base = (is_dir(data_dir) ? data_dir : cfg.config_dir);
	}

	char *trash_base = escape_chars(base, "$\\");
	snprintf(cfg.trash_dir, sizeof(cfg.trash_dir), trash_dir_fmt, trash_base);
	free(trash_base);

	snprintf(cfg.log_file, sizeof(cfg.log_file), "%s/" LOG, base);

	char *fuse_home = format_str("%s/fuse/", base);
	(void)cfg_set_fuse_home(fuse_home);
	free(fuse_home);
}

/* Ensures existence of configuration and data directories.  Performs first run
 * initialization. */
static void
setup_dirs(void)
{
	LOG_FUNC_ENTER;

	char rc_file[PATH_MAX + 8];

	if(is_dir(cfg.config_dir))
	{
		create_scripts_dir();
		return;
	}

	if(make_path(cfg.config_dir, S_IRWXU) != 0)
	{
		return;
	}

	/* This must be first run of Vifm in this environment. */

	copy_rc_file();
	cs_write();

	/* Ensure that just copied sample vifmrc file is used right away. */
	snprintf(rc_file, sizeof(rc_file), "%s/" VIFMRC, cfg.config_dir);
	env_set(MYVIFMRC_EV, rc_file);

	add_default_marks();
}

/* Responsible for creation of scripts/ directory if it doesn't exist (in which
 * case a README file is created as well). */
static void
create_scripts_dir(void)
{
	char scripts[PATH_MAX + 16];
	char readme[PATH_MAX + 32];
	FILE *fp;

	snprintf(scripts, sizeof(scripts), "%s/" SCRIPTS_DIR, cfg.config_dir);
	if(create_path(scripts, S_IRWXU) != 0)
	{
		return;
	}

	snprintf(readme, sizeof(readme), "%s/README", scripts);
	fp = os_fopen(readme, "w");
	if(fp == NULL)
	{
		return;
	}

	fputs("This directory is dedicated for user-supplied scripts/executables.\n"
				"vifm modifies its PATH environment variable to let user run those\n"
				"scripts without specifying full path.  All subdirectories are added\n"
				"as well.  File in a subdirectory overrules file with the same name\n"
				"in parent directories.  Restart might be needed to recognize files\n"
				"in newly created or renamed subdirectories.", fp);

	fclose(fp);
}

/* Copies example vifmrc file from shared files to the ~/.vifm directory. */
static void
copy_rc_file(void)
{
	LOG_FUNC_ENTER;

	char src[PATH_MAX + 16];
	char dst[PATH_MAX + 16];

	io_args_t args = {
		.arg1.src = src,
		.arg2.dst = dst,
	};

	snprintf(src, sizeof(src), "%s/" SAMPLE_VIFMRC, get_installed_data_dir());
	snprintf(dst, sizeof(dst), "%s/" VIFMRC, cfg.config_dir);

	(void)iop_cp(&args);
}

/* Adds 'H' and 'z' default marks. */
static void
add_default_marks(void)
{
	LOG_FUNC_ENTER;

	marks_set_user(curr_view, 'H', cfg.home_dir, NO_MARK_FILE);
	marks_set_user(curr_view, 'z', cfg.config_dir, NO_MARK_FILE);
}

void
cfg_load(void)
{
	const char *rc;
	const int prev_global_local_settings = curr_stats.global_local_settings;

	/* Make changes of usually local settings during configuration affect all
	 * views. */
	curr_stats.global_local_settings = 1;

	/* Try to load global configuration(s). */
	int i = 0;
	while(!vifm_testing())
	{
		const char *conf_dir = get_sys_conf_dir(i++);
		if(conf_dir == NULL)
		{
			break;
		}

		char rc_path[PATH_MAX + 1];
		build_path(rc_path, sizeof(rc_path), conf_dir, VIFMRC);
		(void)cfg_source_file(rc_path);
	}

	/* Try to load local configuration. */
	rc = env_get(MYVIFMRC_EV);
	if(!is_null_or_empty(rc))
	{
		(void)cfg_source_file(rc);
	}

	curr_stats.global_local_settings = prev_global_local_settings;
}

int
cfg_source_file(const char filename[])
{
	/* TODO: maybe move this to commands.c or separate unit eventually. */

	/* Binary mode is important on Windows. */
	FILE *fp = os_fopen(filename, "rb");
	if(fp == NULL)
	{
		return 1;
	}

	strlist_t lines;
	lines.items = read_file_lines(fp, &lines.nitems);
	fclose(fp);

	SourcingState sourcing_state = curr_stats.sourcing_state;
	curr_stats.sourcing_state = SOURCING_PROCESSING;

	int result = source_file_internal(lines, filename);

	curr_stats.sourcing_state = sourcing_state;

	free_string_array(lines.items, lines.nitems);
	return result;
}

/* Returns non-zero on error. */
static int
source_file_internal(strlist_t lines, const char filename[])
{
	if(lines.nitems == 0)
	{
		return 0;
	}

	cmds_scope_start();

	int encoutered_errors = 0;

	char line[MAX_VIFMRC_LINE_LEN + 1];
	line[0] = '\0';

	int line_num = 0;
	int next_line_num = 0;
	for(;;)
	{
		char *next_line = NULL;
		while(next_line_num < lines.nitems)
		{
			next_line = skip_whitespace(lines.items[next_line_num++]);
			if(*next_line == '"')
			{
				continue;
			}
			else if(*next_line == '\\')
			{
				strncat(line, next_line + 1, sizeof(line) - strlen(line) - 1);
				next_line = NULL;
			}
			else
			{
				break;
			}
		}

		ui_sb_clear();

		if(cmds_dispatch(line, curr_view, CIT_COMMAND) < 0)
		{
			show_sourcing_error(filename, line_num);
			encoutered_errors = 1;
		}
		if(curr_stats.sourcing_state == SOURCING_FINISHING)
			break;

		if(next_line == NULL)
		{
			/* Artificially increment line number to simulate as if all that happens
			 * after the loop relates to something past end of the file. */
			line_num++;
			break;
		}

		copy_str(line, sizeof(line), next_line);
		line_num = next_line_num;
	}

	ui_sb_clear();

	if(cmds_scope_finish() != 0)
	{
		show_sourcing_error(filename, line_num);
		encoutered_errors = 1;
	}

	return encoutered_errors;
}

/* Displays sourcing error message to a user. */
static void
show_sourcing_error(const char filename[], int line_num)
{
	const char *const last_msg = ui_sb_last();

	curr_stats.save_msg = 1;

	/* User choice is saved by prompt_error_msgf internally. */
	if(is_null_or_empty(last_msg))
	{
		(void)prompt_error_msgf("File Sourcing Error", "Error in %s at line %d",
				filename, line_num);
	}
	else
	{
		/* The space is needed because empty lines are automatically removed. */
		(void)prompt_error_msgf("File Sourcing Error", "Error in %s at line %d:\n"
				" \n%s", filename, line_num, last_msg);
	}

	curr_stats.save_msg = 0;
}

const char *
cfg_get_vicmd(int *bg)
{
	if(curr_stats.exec_env_type != EET_EMULATOR_WITH_X)
	{
		*bg = cfg.vi_cmd_bg;
		return cfg.vi_command;
	}
	else if(cfg.vi_x_command[0] != '\0')
	{
		*bg = cfg.vi_x_cmd_bg;
		return cfg.vi_x_command;
	}
	else
	{
		*bg = cfg.vi_cmd_bg;
		return cfg.vi_command;
	}
}

void
cfg_clear_histories(int clear_dhistory)
{
	if(clear_dhistory)
	{
		cfg_resize_histories(0);
	}
	else
	{
		hists_resize(0);
		cfg.history_len = 0;
		/* Directory histories keep their data, which will be truncated or updated
		 * later. */
	}
}

void
cfg_resize_histories(int new_size)
{
	hists_resize(new_size);

	int i;
	tab_info_t tab_info;
	for(i = 0; tabs_enum_all(i, &tab_info); ++i)
	{
		flist_hist_resize(tab_info.view, new_size);
	}

	const int old_size = cfg.history_len;
	cfg.history_len = new_size;

	if(old_size <= 0 && new_size > 0)
	{
		int i;
		tab_info_t tab_info;
		for(i = 0; tabs_enum_all(i, &tab_info); ++i)
		{
			flist_hist_save(tab_info.view);
		}
	}
}

int
cfg_set_fuse_home(const char new_value[])
{
#ifdef _WIN32
	char with_forward_slashes[strlen(new_value) + 1];
	strcpy(with_forward_slashes, new_value);
	system_to_internal_slashes(with_forward_slashes);
	new_value = with_forward_slashes;
#endif

	char canonicalized[PATH_MAX + 1];
	canonicalize_path(new_value, canonicalized, sizeof(canonicalized));

	if(!is_path_absolute(new_value))
	{
		if(cfg.fuse_home == NULL)
		{
			/* Do not leave cfg.fuse_home uninitialized. */
			cfg.fuse_home = strdup("");
		}

		show_error_msgf("Error Setting FUSE Home Directory",
				"The path is not absolute: %s", canonicalized);
		return 1;
	}

	return replace_string(&cfg.fuse_home, canonicalized);
}

void
cfg_set_use_term_multiplexer(int use_term_multiplexer)
{
	cfg.use_term_multiplexer = use_term_multiplexer;
	stats_set_use_multiplexer(use_term_multiplexer);
}

void
cfg_set_shell(const char shell[])
{
	if(replace_string(&cfg.shell, shell) == 0)
	{
		stats_update_shell_type(cfg.shell);
	}
}

int
cfg_is_word_wchar(wchar_t c)
{
	return c >= 256 || cfg.word_chars[c];
}

int
cfg_parent_dir_is_visible(int in_root)
{
	return ((in_root && (cfg.dot_dirs & DD_ROOT_PARENT)) ||
			(!in_root && (cfg.dot_dirs & DD_NONROOT_PARENT)));
}

int
cfg_confirm_delete(int to_trash)
{
	return to_trash
	     ? (cfg.confirm & CONFIRM_DELETE)
	     : (cfg.confirm & CONFIRM_PERM_DELETE);
}

int
cfg_ch_pos_on(ChposWhen when)
{
	return cfg.auto_ch_pos && (cfg.ch_pos_on & when);
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
