/* vifm
 * 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 "opt_handlers.h"

#include <curses.h> /* stdscr */
#include <regex.h> /* regex_t regexec() regfree() */

#include <assert.h> /* assert() */
#include <ctype.h> /* isdigit() */
#include <limits.h> /* INT_MAX INT_MIN */
#include <stddef.h> /* NULL size_t */
#include <stdio.h> /* snprintf() */
#include <stdlib.h> /* abs() free() */
#include <string.h> /* memcpy() memmove() strchr() strdup() strlen() strncat()
                       strstr() */

#include "cfg/config.h"
#include "engine/options.h"
#include "engine/text_buffer.h"
#include "int/term_title.h"
#include "ui/fileview.h"
#include "ui/quickview.h"
#include "ui/statusbar.h"
#include "ui/tabs.h"
#include "ui/ui.h"
#include "utils/darray.h"
#include "utils/log.h"
#include "utils/macros.h"
#include "utils/matcher.h"
#include "utils/matchers.h"
#include "utils/regexp.h"
#include "utils/str.h"
#include "utils/string_array.h"
#include "utils/utils.h"
#include "filelist.h"
#include "flist_hist.h"
#include "registers.h"
#include "search.h"
#include "sort.h"
#include "status.h"
#include "trash.h"
#include "types.h"
#include "viewcolumns_parser.h"

/* TODO: provide default primitive type based handlers (see *prg_handler). */

/* Default value of 'viewcolumns' option, used when it's empty. */
#define DEFAULT_VIEW_COLUMNS "-{name},{}"

typedef union
{
	int *bool_val;
	int *int_val;
	char **str_val;
	int *enum_item;
	int *set_items;
}
optvalref_t;

typedef void (*optinit_func)(optval_t *val);

typedef struct
{
	optvalref_t ref;
	optinit_func init;
}
optinit_t;

static void uni_handler(const char name[], optval_t val, OPT_SCOPE scope);
static void init_classify(optval_t *val);
static void init_cpoptions(optval_t *val);
static void init_dirsize(optval_t *val);
static void init_fillchars(optval_t *val);
static void init_hloptions(optval_t *val);
static const char * to_endpoint(int i, char buffer[]);
static void init_trashdir(optval_t *val);
static void init_dotfiles(optval_t *val);
static void init_lsoptions(optval_t *val);
static void init_lsview(optval_t *val);
static void init_milleroptions(optval_t *val);
static void init_millerview(optval_t *val);
static void init_mouse(optval_t *val);
static void init_navoptions(optval_t *val);
static void init_previewoptions(optval_t *val);
static void init_quickview(optval_t *val);
static void init_shortmess(optval_t *val);
static void init_sizefmt(optval_t *val);
static void init_iooptions(optval_t *val);
static void init_number(optval_t *val);
static void init_numberwidth(optval_t *val);
static void init_relativenumber(optval_t *val);
static void init_sort(optval_t *val);
static void init_sortorder(optval_t *val);
static void init_tuioptions(optval_t *val);
static void init_uioptions(optval_t *val);
static void init_wildinc(optval_t *val);
static void init_wordchars(optval_t *val);
static void load_options_defaults(void);
static void add_options(void);
static void load_sort_option_inner(view_t *view, signed char sort_keys[]);
static void aproposprg_handler(OPT_OP op, optval_t val);
static void autocd_handler(OPT_OP op, optval_t val);
static void autochpos_handler(OPT_OP op, optval_t val);
static void caseoptions_handler(OPT_OP op, optval_t val);
static void cdpath_handler(OPT_OP op, optval_t val);
static void chaselinks_handler(OPT_OP op, optval_t val);
static void classify_handler(OPT_OP op, optval_t val);
static int str_to_classify(const char str[], char type_decs[FT_COUNT][2][9]);
static const char * pick_out_decoration(char classify_item[], FileType *type,
		const char **expr);
static int validate_decorations(const char prefix[], const char suffix[]);
static void free_file_decs(file_dec_t *name_decs, int count);
static void columns_handler(OPT_OP op, optval_t val);
static void confirm_handler(OPT_OP op, optval_t val);
static void cpoptions_handler(OPT_OP op, optval_t val);
static void cvoptions_handler(OPT_OP op, optval_t val);
static void deleteprg_handler(OPT_OP op, optval_t val);
static void dirsize_handler(OPT_OP op, optval_t val);
static void dotdirs_handler(OPT_OP op, optval_t val);
static void fastrun_handler(OPT_OP op, optval_t val);
static void fillchars_handler(OPT_OP op, optval_t val);
static void load_fillchars(void);
static void findprg_handler(OPT_OP op, optval_t val);
static void followlinks_handler(OPT_OP op, optval_t val);
static void fusehome_handler(OPT_OP op, optval_t val);
static void gdefault_handler(OPT_OP op, optval_t val);
static void grepprg_handler(OPT_OP op, optval_t val);
static void histcursor_handler(OPT_OP op, optval_t val);
static void history_handler(OPT_OP op, optval_t val);
static void hloptions_handler(OPT_OP op, optval_t val);
static void hlsearch_handler(OPT_OP op, optval_t val);
static void iec_handler(OPT_OP op, optval_t val);
static void ignorecase_handler(OPT_OP op, optval_t val);
static void incsearch_handler(OPT_OP op, optval_t val);
static void iooptions_handler(OPT_OP op, optval_t val);
static void laststatus_handler(OPT_OP op, optval_t val);
static void lines_handler(OPT_OP op, optval_t val);
static void locateprg_handler(OPT_OP op, optval_t val);
#ifndef _WIN32
static void mediaprg_handler(OPT_OP op, optval_t val);
#endif
static void mintimeoutlen_handler(OPT_OP op, optval_t val);
static void scroll_line_down(view_t *view);
static void mouse_handler(OPT_OP op, optval_t val);
static void navoptions_handler(OPT_OP op, optval_t val);
static void previewoptions_handler(OPT_OP op, optval_t val);
static void quickview_handler(OPT_OP op, optval_t val);
static void rulerformat_handler(OPT_OP op, optval_t val);
static void runexec_handler(OPT_OP op, optval_t val);
static void scrollbind_handler(OPT_OP op, optval_t val);
static void scrolloff_handler(OPT_OP op, optval_t val);
static void shell_handler(OPT_OP op, optval_t val);
static void shellcmdflag_handler(OPT_OP op, optval_t val);
static void shortmess_handler(OPT_OP op, optval_t val);
static void showtabline_handler(OPT_OP op, optval_t val);
static void sizefmt_handler(OPT_OP op, optval_t val);
static optval_t make_sizefmt_value(void);
#ifndef _WIN32
static void slowfs_handler(OPT_OP op, optval_t val);
#endif
static void smartcase_handler(OPT_OP op, optval_t val);
static void sortnumbers_handler(OPT_OP op, optval_t val);
static void dotfiles_global(OPT_OP op, optval_t val);
static void dotfiles_local(OPT_OP op, optval_t val);
static void lsoptions_global(OPT_OP op, optval_t val);
static void lsoptions_local(OPT_OP op, optval_t val);
static void set_lsoptions(int *transposed, int *column_count, optval_t val,
		OPT_SCOPE scope);
static void fill_lsoptions(optval_t *val, int transposed, int column_count);
static void lsview_global(OPT_OP op, optval_t val);
static void lsview_local(OPT_OP op, optval_t val);
static void milleroptions_global(OPT_OP op, optval_t val);
static void milleroptions_local(OPT_OP op, optval_t val);
static void set_milleroptions(int ratios[3], MillerPreview *preview,
		optval_t val, OPT_SCOPE scope);
static void fill_milleroptions(optval_t *val, int ratios[3],
		MillerPreview preview);
static void millerview_global(OPT_OP op, optval_t val);
static void millerview_local(OPT_OP op, optval_t val);
static void number_global(OPT_OP op, optval_t val);
static void number_local(OPT_OP op, optval_t val);
static void numberwidth_global(OPT_OP op, optval_t val);
static void numberwidth_local(OPT_OP op, optval_t val);
static void set_numberwidth(view_t *view, int *num_width, int width);
static void previewprg_global(OPT_OP op, optval_t val);
static void previewprg_local(OPT_OP op, optval_t val);
static void relativenumber_global(OPT_OP op, optval_t val);
static void relativenumber_local(OPT_OP op, optval_t val);
static void update_num_type(view_t *view, NumberingType *num_type,
		NumberingType type, int enable);
static void sort_global(OPT_OP op, optval_t val);
static void sort_local(OPT_OP op, optval_t val);
static void set_sort(view_t *view, signed char sort_keys[], char order[]);
static void sortgroups_global(OPT_OP op, optval_t val);
static void sortgroups_local(OPT_OP op, optval_t val);
static void set_sortgroups(view_t *view, char **opt, char value[]);
static void sorting_changed(view_t *view, int defer_slow);
static void sortorder_global(OPT_OP op, optval_t val);
static void sortorder_local(OPT_OP op, optval_t val);
static void set_sortorder(view_t *view, int ascending, signed char sort_keys[]);
static void viewcolumns_global(OPT_OP op, optval_t val);
static void viewcolumns_local(OPT_OP op, optval_t val);
static void set_viewcolumns(view_t *view, const char view_columns[]);
static void set_view_columns_option(view_t *view, const char value[],
		int update_ui);
static void add_column(columns_t *columns, column_info_t column_info);
static int map_name(const char name[], void *arg);
static void resort_view(view_t * view);
static void sessionoptions_handler(OPT_OP op, optval_t val);
static void statusline_handler(OPT_OP op, optval_t val);
static void suggestoptions_handler(OPT_OP op, optval_t val);
static void reset_suggestoptions(void);
static void syncregs_handler(OPT_OP op, optval_t val);
static void syscalls_handler(OPT_OP op, optval_t val);
static void tablabel_handler(OPT_OP op, optval_t val);
static void tabline_handler(OPT_OP op, optval_t val);
static void tabprefix_handler(OPT_OP op, optval_t val);
static void tabscope_handler(OPT_OP op, optval_t val);
static void tabstop_handler(OPT_OP op, optval_t val);
static void tabsuffix_handler(OPT_OP op, optval_t val);
static void timefmt_handler(OPT_OP op, optval_t val);
static void timeoutlen_handler(OPT_OP op, optval_t val);
static void title_handler(OPT_OP op, optval_t val);
static void trash_handler(OPT_OP op, optval_t val);
static void trashdir_handler(OPT_OP op, optval_t val);
static void tuioptions_handler(OPT_OP op, optval_t val);
static void uioptions_handler(OPT_OP op, optval_t val);
static void undolevels_handler(OPT_OP op, optval_t val);
static void vicmd_handler(OPT_OP op, optval_t val);
static void vixcmd_handler(OPT_OP op, optval_t val);
static void vifminfo_handler(OPT_OP op, optval_t val);
static void vimhelp_handler(OPT_OP op, optval_t val);
static void wildinc_handler(OPT_OP op, optval_t val);
static void wildmenu_handler(OPT_OP op, optval_t val);
static void wildstyle_handler(OPT_OP op, optval_t val);
static void wordchars_handler(OPT_OP op, optval_t val);
static int parse_range(const char range[], int *from, int *to);
static int parse_endpoint(const char **str, int *endpoint);
static void wrap_handler(OPT_OP op, optval_t val);
static void text_option_changed(void);
static void wrapscan_handler(OPT_OP op, optval_t val);

/* Possible values of 'caseoptions'. */
static const char *caseoptions_vals[][2] = {
	{ "pPgG", "all caseoptions values" },
	{ "p", "always ignore case of paths during completion" },
	{ "P", "always match case of paths during completion" },
	{ "g", "always ignore case of characters for f/F/;/," },
	{ "G", "always match case of characters for f/F/;/," },
};
ARRAY_GUARD(caseoptions_vals, 1 + NUM_CASE_OPTS*2);

/* Possible values of 'cpoptions'. */
static const char *cpoptions_vals[][2] = {
	{ "fst", "all cpoptions values" },
	{ "f", "leave files that match filter by default" },
	{ "s", "use selection for yy, dd and DD, when present" },
	{ "t", "switch active pane via <tab>" },
};

/* Possible values of 'cvoptions'. */
static const char *cvoptions_vals[][2] = {
	{ "autocmds", "trigger autocommands on entering/leaving custom views" },
	{ "localopts", "reset local options on entering/leaving custom views" },
	{ "localfilter", "reset local filter on entering/leaving custom views" },
};

/* Possible values of 'confirm'. */
static const char *confirm_vals[][2] = {
	{ "delete",     "confirm moving to trash" },
	{ "permdelete", "confirm permanent removal" },
};

/* Possible values of 'dotdirs'. */
static const char *dotdirs_vals[][2] = {
	[BIT(DD_ROOT_PARENT)]       = { "rootparent",
	                                "show .. in FS root" },
	[BIT(DD_NONROOT_PARENT)]    = { "nonrootparent",
	                                "show .. in non-root directories" },
	[BIT(DD_TREE_LEAFS_PARENT)] = { "treeleafsparent",
	                                "show .. in empty directories of :tree" },
};
ARRAY_GUARD(dotdirs_vals, NUM_DOT_DIRS);

/* Possible values of 'histcursor'. */
static const char *histcursor_vals[][2] = {
	[BIT(CHPOS_STARTUP)] = { "startup",  "initial view loading on startup" },
	[BIT(CHPOS_DIRMARK)] = { "dirmark",  "after navigating to a mark w/o file" },
	[BIT(CHPOS_ENTER)] =   { "direnter", "after picking directory in file list" },
};
ARRAY_GUARD(histcursor_vals, NUM_CHPOS);

/* Possible keys of 'hloptions' option. */
static const char *hloptions_enum[][2] = {
	{ "filehi:", "when to use file highlight: path, onerow or allrows" },
};

/* Possible keys of 'lsoptions' option. */
static const char *lsoptions_enum[][2] = {
	{ "columncount:", "fixed number of columns to display or 0" },
	{ "transposed",   "fill grid by column instead of by line" },
};

/* Possible values of 'mouse'. */
static const char *mouse_vals[][2] = {
	{ "acmnqv", "all mouse values" },
	{ "a", "all supported modes" },
	{ "c", "command-line mode" },
	{ "m", "menu mode" },
	{ "n", "normal mode" },
	{ "q", "view mode" },
	{ "v", "visual mode" },
};
ARRAY_GUARD(mouse_vals, 1 + NUM_M_OPTS);

/* Possible values of 'navoptions'. */
static const char *navoptions_vals[][2] = {
	{ "open:", "what entries to open on enter: dirs or all" },
};

/* Possible values of 'previewoptions'. */
static const char *previewoptions_vals[][2] = {
	{ "graphicsdelay:",    "delay before drawing graphics" },
	{ "hardgraphicsclear", "redraw screen to get rid of graphics" },
	{ "maxtreedepth:",     "how many tree levels to display" },
	{ "toptreestats",      "show file counts on top of the tree" },
};

/* Possible values of 'suggestoptions'. */
static const char *suggestoptions_vals[][2] = {
	[BIT(SF_NORMAL)]      = { "normal",      "display in normal mode" },
	[BIT(SF_VISUAL)]      = { "visual",      "display in visual mode" },
	[BIT(SF_VIEW)]        = { "view",        "display in view mode" },
	[BIT(SF_OTHERPANE)]   = { "otherpane",
	                          "use other pane for suggestions, if available" },
	[BIT(SF_DELAY)]       = { "delay",
	                          "display suggestions after a short delay" },
	[BIT(SF_KEYS)]        = { "keys",
	                          "include keys suggestions in results" },
	[BIT(SF_MARKS)]       = { "foldsubkeys",
	                          "fold multiple keys with common prefix" },
	[BIT(SF_REGISTERS)]   = { "marks",
	                          "include marks suggestions in results" },
	[BIT(SF_FOLDSUBKEYS)] = { "registers",
	                          "include registers suggestions in results" },
};
ARRAY_GUARD(suggestoptions_vals, NUM_SUGGESTION_FLAGS);

/* Possible flags of 'iooptions'. */
static const char *iooptions_vals[][2] = {
	{ "fastfilecloning", "use COW if FS supports it" },
	{ "datasync",        "synchronize writes to storage" },
};

/* Possible flags of 'shortmess' and their count. */
static const char *shortmess_vals[][2] = {
	{ "LMTp", "all shortmess values" },
	{ "L",    "display only last directory in tab line instead of full path" },
	{ "M",    "use only file name for title in terminal multiplexers" },
	{ "T",    "shorten too long status bar messages" },
	{ "p",    "substitute home path with ~ in view title" },
};

/* Possible values of 'showtabline'. */
static const char *showtabline_vals[][2] = {
	{ "never",    "tab line is never displayed" },
	{ "multiple", "tab line is visible when there are at least two tabs" },
	{ "always",   "tab line is displayed always" },
	{ "0",        "tab line is never displayed" },
	{ "1",        "tab line is visible when there are at least two tabs" },
	{ "2",        "tab line is displayed always" },
};

/* Possible values of 'tabscope'. */
static const char *tabscope_vals[][2] = {
	{ "global", "single tab contains panes and their layout" },
	{ "pane",   "tab contains state of just one pane" },
};

/* Possible flags of 'tuioptions' and their count. */
static const char *tuioptions_vals[][2] = {
	{ "lprsuv", "all tuioptions values" },
	{ "l",      "always show pane title ellipsis on the left" },
	{ "p",      "use padding in views and preview" },
	{ "r",      "always show pane title ellipsis on the right" },
	{ "s",      "display side borders" },
	{ "u",      "use Unicode characters in the TUI" },
	{ "v",      "vary width of middle border to equalize view sizes" },
};

/* Possible flags of 'uioptions'. */
static const char *uioptions_vals[][2] = {
	{ "iodetails", "show I/O progress details dialog automatically" },
};

/* Possible values of 'dirsize' option. */
static const char *dirsize_enum[][2] = {
	{ "size",   "size of directory or its files" },
	{ "nitems", "number of files in directory" },
};

/* Possible keys of 'fillchars' option. */
static const char *fillchars_enum[][2] = {
	{ "hborder:",   "filler of horizontal border" },
	{ "millersep:", "separator of miller columns" },
	{ "vborder:",   "filler of vertical borders" },
};

/* Possible keys of 'milleroptions' option. */
static const char *milleroptions_enum[][2] = {
	{ "lsize:",    "proportion of space given to the left column" },
	{ "csize:",    "proportion of space given to the center column" },
	{ "rsize:",    "proportion of space given to the right column" },
	{ "rpreview:", "what should right pane preview: dirs, files or all" },
};

/* Possible keys of 'sizefmt' option. */
static const char *sizefmt_enum[][2] = {
	{ "units:",     "iec (1024) or si (1000) unit size" },
	{ "precision:", "maximum width of fraction part" },
	{ "space",      "show separator space" },
	{ "nospace",    "hide separator space" },
};

/* Possible values of 'sort' option. */
static const char *sort_types[][2] = {
	{ "ext",  "by file/directory extension" },
	{ "+ext", "by file/directory extension" },
	{ "-ext", "by file/directory extension" },

	{ "name",  "by name (case-sensitive)" },
	{ "+name", "by name (case-sensitive)" },
	{ "-name", "by name (case-sensitive)" },

	{ "size",  "by size" },
	{ "+size", "by size" },
	{ "-size", "by size" },

	{ "atime",  "by access time" },
	{ "+atime", "by access time" },
	{ "-atime", "by access time" },

#ifndef _WIN32
	{ "ctime",  "by change time" },
	{ "+ctime", "by change time" },
	{ "-ctime", "by change time" },
#else
	{ "ctime",  "by creation time" },
	{ "+ctime", "by creation time" },
	{ "-ctime", "by creation time" },
#endif

	{ "mtime",  "by modification time" },
	{ "+mtime", "by modification time" },
	{ "-mtime", "by modification time" },

	{ "iname",  "by name (case-insensitive)" },
	{ "+iname", "by name (case-insensitive)" },
	{ "-iname", "by name (case-insensitive)" },

	{ "dir",  "by `is directory` predicate" },
	{ "+dir", "by `is directory` predicate" },
	{ "-dir", "by `is directory` predicate" },

	{ "type",  "by file type" },
	{ "+type", "by file type" },
	{ "-type", "by file type" },

	{ "fileext",  "by file (no directories) extension" },
	{ "+fileext", "by file (no directories) extension" },
	{ "-fileext", "by file (no directories) extension" },

	{ "nitems",  "by number of files in directory" },
	{ "+nitems", "by number of files in directory" },
	{ "-nitems", "by number of files in directory" },

	{ "groups",  "by 'sortgroups' match" },
	{ "+groups", "by 'sortgroups' match" },
	{ "-groups", "by 'sortgroups' match" },

	{ "target",  "by symbolic link target" },
	{ "+target", "by symbolic link target" },
	{ "-target", "by symbolic link target" },

#ifndef _WIN32
	{ "gid",  "by group ID" },
	{ "+gid", "by group ID" },
	{ "-gid", "by group ID" },

	{ "gname",  "by group name" },
	{ "+gname", "by group name" },
	{ "-gname", "by group name" },

	{ "mode",  "by file mode" },
	{ "+mode", "by file mode" },
	{ "-mode", "by file mode" },

	{ "inode",  "by file inode number" },
	{ "+inode", "by file inode number" },
	{ "-inode", "by file inode number" },

	{ "uid",  "by user ID" },
	{ "+uid", "by user ID" },
	{ "-uid", "by user ID" },

	{ "uname",  "by user name" },
	{ "+uname", "by user name" },
	{ "-uname", "by user name" },

	{ "perms",  "by permissions" },
	{ "+perms", "by permissions" },
	{ "-perms", "by permissions" },

	{ "nlinks",  "by number of hard-links" },
	{ "+nlinks", "by number of hard-links" },
	{ "-nlinks", "by number of hard-links" },
#endif
};
ARRAY_GUARD(sort_types, SK_COUNT*3);

/* Possible values of 'sortorder' option. */
static const char *sortorder_enum[][2] = {
	{ "ascending",  "increasing ordering" },
	{ "descending", "decreasing ordering" },
};

/* Possible values of 'vifminfo' option. */
static const char *vifminfo_set[][2] = {
	[BIT(VINFO_OPTIONS)]   = { "options",   "values of options" },
	[BIT(VINFO_FILETYPES)] = { "filetypes", "file associations" },
	[BIT(VINFO_COMMANDS)]  = { "commands",  "user-defined :commands" },
	[BIT(VINFO_MARKS)]     = { "bookmarks", "marks (e.g. 'a)" },
	[BIT(VINFO_BOOKMARKS)] = { "bmarks",    "bookmarks" },
	[BIT(VINFO_TUI)]       = { "tui",       "state of TUI" },
	[BIT(VINFO_DHISTORY)]  = { "dhistory",  "directory history" },
	[BIT(VINFO_STATE)]     = { "state",     "filters and terminal multiplexer" },
	[BIT(VINFO_CS)]        = { "cs",        "current colorscheme" },
	[BIT(VINFO_SAVEDIRS)]  = { "savedirs",  "restore last visited directory" },
	[BIT(VINFO_CHISTORY)]  = { "chistory",  "cmdline history" },
	[BIT(VINFO_SHISTORY)]  = { "shistory",  "search history" },
	[BIT(VINFO_DIRSTACK)]  = { "dirstack",  "directory stack" },
	[BIT(VINFO_REGISTERS)] = { "registers", "contents of registers" },
	[BIT(VINFO_PHISTORY)]  = { "phistory",  "prompt history" },
	[BIT(VINFO_EHISTORY)]  = { "ehistory",  "expression register history" },
	[BIT(VINFO_FHISTORY)]  = { "fhistory",  "local filter history" },
	[BIT(VINFO_MCHISTORY)] = { "mchistory", "menu cmdline history" },
	[BIT(VINFO_TABS)]      = { "tabs",      "global or pane tabs" },
};
ARRAY_GUARD(vifminfo_set, NUM_VINFO);

/* Possible values of 'wildstyle'. */
static const char *wildstyle_vals[][2] = {
	{ "bar",   "single-line bar" },
	{ "popup", "multi-line popup" },
};
ARRAY_GUARD(wildstyle_vals, 2);

/* Empty value to satisfy default initializer. */
static char empty_val[] = "";
static char *empty = empty_val;

/* Specification of options. */
static struct opt_data_t
{
	const char *name;           /* Full name of the option. */
	const char *abbr;           /* Abbreviated option name. */
	const char *descr;          /* Brief option description. */
	OPT_TYPE type;              /* Option type (boolean, integer, etc.). */
	int val_count;              /* Size of vals. */
	const char *(*vals)[2];     /* Array of possible values. */
	opt_handler global_handler; /* Global option handler. */
	opt_handler local_handler;  /* Local option handler (can be NULL). */
	optinit_t initializer;      /* Definition of source of initial value. */
	optval_t val;               /* Storage for initial value. */
}
options[] = {
	/* Global options. */
	{ "aproposprg", "", ":apropos invocation format",
	  OPT_STR, 0, NULL, &aproposprg_handler, NULL,
	  { .ref.str_val = &cfg.apropos_prg },
	},
	{ "autocd", "", "interpret invalid :commands as directory change command",
	  OPT_BOOL, 0, NULL, &autocd_handler, NULL,
	  { .ref.bool_val = &cfg.auto_cd },
	},
	{ "autochpos", "", "restore cursor after cd",
	  OPT_BOOL, 0, NULL, &autochpos_handler, NULL,
	  { .ref.bool_val = &cfg.auto_ch_pos },
	},
	{ "caseoptions", "", "case sensitivity overrides",
	  OPT_CHARSET, ARRAY_LEN(caseoptions_vals), caseoptions_vals,
	  &caseoptions_handler, NULL,
	  { .ref.str_val = &empty },
	},
	{ "cdpath", "cd", "list of prefixes for relative cd",
	  OPT_STRLIST, 0, NULL, &cdpath_handler, NULL,
	  { .ref.str_val = &cfg.cd_path },
	},
	{ "chaselinks", "", "resolve links in view path",
	  OPT_BOOL, 0, NULL, &chaselinks_handler, NULL,
	  { .ref.bool_val = &cfg.chase_links },
	},
	{ "classify", "", "file name decorations",
	  OPT_STRLIST, 0, NULL, &classify_handler, NULL,
	  { .init = &init_classify },
	},
	{ "columns", "co", "width of TUI in chars",
	  OPT_INT, 0, NULL, &columns_handler, NULL,
	  { .ref.int_val = &cfg.columns },
	},
	{ "confirm", "cf", "confirm file operations",
	  OPT_SET, ARRAY_LEN(confirm_vals), confirm_vals, &confirm_handler, NULL,
	  { .ref.bool_val = &cfg.confirm },
	},
	{ "cpoptions", "cpo", "compatibility options",
	  OPT_CHARSET, ARRAY_LEN(cpoptions_vals), cpoptions_vals, &cpoptions_handler,
	  NULL,
	  { .init = &init_cpoptions },
	},
	{ "cvoptions", "", "configures affects cv enter/leave",
	  OPT_SET, ARRAY_LEN(cvoptions_vals), cvoptions_vals, &cvoptions_handler,
	  NULL,
	  { .ref.int_val = &cfg.cvoptions },
	},
	{ "deleteprg", "", "permanent file deletion program",
	  OPT_STR, 0, NULL, &deleteprg_handler, NULL,
	  { .ref.str_val = &empty },
	},
	{ "dirsize", "", "directory size style",
	  OPT_ENUM, ARRAY_LEN(dirsize_enum), dirsize_enum, &dirsize_handler, NULL,
	  { .init = &init_dirsize },
	},
	{ "dotdirs", "", "which dot directories to show",
	  OPT_SET, ARRAY_LEN(dotdirs_vals), dotdirs_vals, &dotdirs_handler, NULL,
	  { .ref.set_items = &cfg.dot_dirs },
	},
	{ "fastrun", "", "autocomplete unambiguous prefixes for :!",
	  OPT_BOOL, 0, NULL, &fastrun_handler, NULL,
	  { .ref.bool_val = &cfg.fast_run },
	},
	{ "fillchars", "fcs", "fillers for interface parts",
	  OPT_STRLIST, ARRAY_LEN(fillchars_enum), fillchars_enum, &fillchars_handler,
	  NULL,
	  { .init = &init_fillchars },
	},
	{ "findprg", "", ":find invocation format",
	  OPT_STR, 0, NULL, &findprg_handler, NULL,
	  { .ref.str_val = &cfg.find_prg },
	},
	{ "followlinks", "", "go to target file on opening links",
	  OPT_BOOL, 0, NULL, &followlinks_handler, NULL,
	  { .ref.bool_val = &cfg.follow_links },
	},
	{ "fusehome", "", "base directory for FUSE mounts",
	  OPT_STR, 0, NULL, &fusehome_handler, NULL,
	  { .ref.str_val = &cfg.fuse_home },
	},
	{ "gdefault", "gd", "global :substitute by default",
	  OPT_BOOL, 0, NULL, &gdefault_handler, NULL,
	  { .ref.bool_val = &cfg.gdefault },
	},
	{ "grepprg", "", ":grep invocation format",
	  OPT_STR, 0, NULL, &grepprg_handler, NULL,
	  { .ref.str_val = &cfg.grep_prg },
	},
	{ "histcursor", "", "when to use historical cursor position",
	  OPT_SET, ARRAY_LEN(histcursor_vals), histcursor_vals,
	  &histcursor_handler, NULL,
	  { .ref.set_items = &cfg.ch_pos_on },
	},
	{ "hloptions", "", "settings for how highlighting is done",
	  OPT_STRLIST, ARRAY_LEN(hloptions_enum), hloptions_enum,
	  &hloptions_handler, NULL,
	  { .init = &init_hloptions },
	},
	{ "history", "hi", "length of all histories",
	  OPT_INT, 0, NULL, &history_handler, NULL,
	  { .ref.int_val = &cfg.history_len },
	},
	{ "hlsearch", "hls", "autoselect search matches",
	  OPT_BOOL, 0, NULL, &hlsearch_handler, NULL,
	  { .ref.bool_val = &cfg.hl_search },
	},
	{ "iec", "", "use IEC notation for sizes",
	  OPT_BOOL, 0, NULL, &iec_handler, NULL,
	  { .ref.bool_val = &cfg.sizefmt.ieci_prefixes },
	},
	{ "ignorecase", "ic", "ignore case on searches",
	  OPT_BOOL, 0, NULL, &ignorecase_handler, NULL,
	  { .ref.bool_val = &cfg.ignore_case },
	},
	{ "incsearch", "is", "incremental (live) search",
	  OPT_BOOL, 0, NULL, &incsearch_handler, NULL,
	  { .ref.bool_val = &cfg.inc_search },
	},
	{ "iooptions", "", "file I/O settings",
	  OPT_SET, ARRAY_LEN(iooptions_vals), iooptions_vals, &iooptions_handler,
	  NULL,
	  { .init = &init_iooptions },
	},
	{ "laststatus", "ls", "visibility of status bar",
	  OPT_BOOL, 0, NULL, &laststatus_handler, NULL,
	  { .ref.bool_val = &cfg.display_statusline },
	},
	{ "lines", "", "height of TUI in chars",
	  OPT_INT, 0, NULL, &lines_handler, NULL,
	  { .ref.int_val = &cfg.lines },
	},
	{ "locateprg", "", ":locate invocation format",
	  OPT_STR, 0, NULL, &locateprg_handler, NULL,
	  { .ref.str_val = &cfg.locate_prg },
	},
#ifndef _WIN32
	{ "mediaprg", "", "helper for :media menu",
	  OPT_STR, 0, NULL, &mediaprg_handler, NULL,
	  { .ref.str_val = &cfg.media_prg },
	},
#endif
	{ "mintimeoutlen", "", "delay between input polls",
	  OPT_INT, 0, NULL, &mintimeoutlen_handler, NULL,
	  { .ref.int_val = &cfg.min_timeout_len },
	},
	{ "mouse", "", "which modes handle mouse",
	  OPT_CHARSET, ARRAY_LEN(mouse_vals), mouse_vals, &mouse_handler, NULL,
	  { .init = &init_mouse },
	},
	{ "navoptions", "", "tweaks for navigation mode",
	  OPT_STRLIST, ARRAY_LEN(navoptions_vals), navoptions_vals,
	  &navoptions_handler, NULL,
	  { .init = &init_navoptions },
	},
	{ "previewoptions", "", "tweaks for how preview is done",
	  OPT_STRLIST, ARRAY_LEN(previewoptions_vals), previewoptions_vals,
	  &previewoptions_handler, NULL,
	  { .init = &init_previewoptions },
	},
	{ "quickview", "", "whether quick view is active",
	  OPT_BOOL, 0, NULL, &quickview_handler, NULL,
	  { .init = &init_quickview },
	},
	{ "rulerformat", "ruf", "format of the ruler",
	  OPT_STR, 0, NULL, &rulerformat_handler, NULL,
	  { .ref.str_val = &cfg.ruler_format },
	},
	{ "runexec", "", "run executables on opening them",
	  OPT_BOOL, 0, NULL, &runexec_handler, NULL,
	  { .ref.bool_val = &cfg.auto_execute },
	},
	{ "scrollbind", "scb", "synchronize scroll position in two panes",
	  OPT_BOOL, 0, NULL, &scrollbind_handler, NULL,
	  { .ref.bool_val = &cfg.scroll_bind },
	},
	{ "scrolloff", "so", "minimal cursor distance to view borders",
	  OPT_INT, 0, NULL, &scrolloff_handler, NULL,
	  { .ref.int_val = &cfg.scroll_off },
	},
	{ "sessionoptions", "ssop", "what to store in a session file",
	  OPT_SET, ARRAY_LEN(vifminfo_set), vifminfo_set, &sessionoptions_handler,
	  NULL,
	  { .ref.set_items = &cfg.session_options },
	},
	{ "shell", "sh", "shell to run external commands",
	  OPT_STR, 0, NULL, &shell_handler, NULL,
	  { .ref.str_val = &cfg.shell },
	},
	{ "shellcmdflag", "shcf", "argument for the shell",
	  OPT_STR, 0, NULL, &shellcmdflag_handler, NULL,
	  { .ref.str_val = &cfg.shell_cmd_flag },
	},
	{ "shortmess", "shm", "things to shorten in TUI",
	  OPT_CHARSET, ARRAY_LEN(shortmess_vals), shortmess_vals, &shortmess_handler,
	  NULL,
	  { .init = &init_shortmess },
	},
	{ "showtabline", "stal", "when to display tab line",
	  OPT_ENUM, ARRAY_LEN(showtabline_vals), showtabline_vals,
	  &showtabline_handler, NULL,
	  { .ref.enum_item = &cfg.show_tab_line },
	},
	{ "sizefmt", "", "human-friendly size format",
	  OPT_STRLIST, ARRAY_LEN(sizefmt_enum), sizefmt_enum, &sizefmt_handler, NULL,
	  { .init = &init_sizefmt },
	},
#ifndef _WIN32
	{ "slowfs", "", "list of slow filesystem types",
	  OPT_STRLIST, 0, NULL, &slowfs_handler, NULL,
	  { .ref.str_val = &cfg.slow_fs_list },
	},
#endif
	{ "smartcase", "scs", "pick pattern sensitivity based on case",
	  OPT_BOOL, 0, NULL, &smartcase_handler, NULL,
	  { .ref.bool_val = &cfg.smart_case },
	},
	{ "sortnumbers", "", "version sorting for files",
	  OPT_BOOL, 0, NULL, &sortnumbers_handler, NULL,
	  { .ref.bool_val = &cfg.sort_numbers },
	},
	{ "statusline", "stl", "format of the status line",
	  OPT_STR, 0, NULL, &statusline_handler, NULL,
	  { .ref.str_val = &cfg.status_line },
	},
	{ "suggestoptions", "", "when and how to display key suggestions",
	  OPT_STRLIST, ARRAY_LEN(suggestoptions_vals), suggestoptions_vals,
	  &suggestoptions_handler, NULL,
	  { .ref.str_val = &empty },
	},
	{ "syncregs", "", "defines group of instances that share registers",
	  OPT_STR, 0, NULL, &syncregs_handler, NULL,
	  { .ref.str_val = &empty } },
	{ "syscalls", "", "use system calls for file operations",
	  OPT_BOOL, 0, NULL, &syscalls_handler, NULL,
	  { .ref.bool_val = &cfg.use_system_calls },
	},
	{ "tablabel", "", "format of main part of a single tab's label",
	  OPT_STR, 0, NULL, &tablabel_handler, NULL,
	  { .ref.str_val = &cfg.tab_label },
	},
	{ "tabline", "tal", "format of the tab line",
	  OPT_STR, 0, NULL, &tabline_handler, NULL,
	  { .ref.str_val = &cfg.tab_line },
	},
	{ "tabprefix", "", "format of prefix of a tab's label",
	  OPT_STR, 0, NULL, &tabprefix_handler, NULL,
	  { .ref.str_val = &cfg.tab_prefix },
	},
	{ "tabscope", "", "level at which tabs operate",
	  OPT_ENUM, ARRAY_LEN(tabscope_vals), tabscope_vals, &tabscope_handler, NULL,
	  { .ref.bool_val = &cfg.pane_tabs },
	},
	{ "tabstop", "ts", "widths of one tabulation",
	  OPT_INT, 0, NULL, &tabstop_handler, NULL,
	  { .ref.int_val = &cfg.tab_stop },
	},
	{ "tabsuffix", "", "format of suffix of a tab's label",
	  OPT_STR, 0, NULL, &tabsuffix_handler, NULL,
	  { .ref.str_val = &cfg.tab_suffix },
	},
	{ "timefmt", "", "time format",
	  OPT_STR, 0, NULL, &timefmt_handler, NULL,
	  { .ref.str_val = &cfg.time_format },
	},
	{ "timeoutlen", "tm", "delay on ambiguous key sequence",
	  OPT_INT, 0, NULL, &timeoutlen_handler, NULL,
	  { .ref.int_val = &cfg.timeout_len },
	},
	{ "title", "", "set terminal title",
	  OPT_BOOL, 0, NULL, &title_handler, NULL,
	  { .ref.int_val = &cfg.set_title },
	},
	{ "trash", "", "put files to trash on removing them",
	  OPT_BOOL, 0, NULL, &trash_handler, NULL,
	  { .ref.bool_val = &cfg.use_trash },
	},
	{ "trashdir", "", "path to trash directory",
	  OPT_STRLIST, 0, NULL, &trashdir_handler, NULL,
	  { .init = &init_trashdir },
	},
	{ "tuioptions", "to", "TUI look tweaks",
	  OPT_CHARSET, ARRAY_LEN(tuioptions_vals), tuioptions_vals,
	  &tuioptions_handler, NULL,
	  { .init = &init_tuioptions },
	},
	{ "uioptions", "", "tweaks of UI behaviour",
	  OPT_SET, ARRAY_LEN(uioptions_vals), uioptions_vals,
	  &uioptions_handler, NULL,
	  { .init = &init_uioptions },
	},
	{ "undolevels", "ul", "number of file operations to remember",
	  OPT_INT, 0, NULL, &undolevels_handler, NULL,
	  { .ref.int_val = &cfg.undo_levels },
	},
	{ "vicmd", "", "command to invoke editor in console",
	  OPT_STR, 0, NULL, &vicmd_handler, NULL,
	  { .ref.str_val = &cfg.vi_command },
	},
	{ "vixcmd", "", "command to invoke editor in X",
	  OPT_STR, 0, NULL, &vixcmd_handler, NULL,
	  { .ref.str_val = &cfg.vi_x_command },
	},
	{ "vifminfo", "", "what to store in vifminfo",
	  OPT_SET, ARRAY_LEN(vifminfo_set), vifminfo_set, &vifminfo_handler, NULL,
	  { .ref.set_items = &cfg.vifm_info },
	},
	{ "vimhelp", "", "use Vim help instead of plain text",
	  OPT_BOOL, 0, NULL, &vimhelp_handler, NULL,
	  { .ref.bool_val = &cfg.use_vim_help },
	},
	{ "wildinc", "", "what to complete incrementally (live)",
	  OPT_STR, 0, NULL, &wildinc_handler, NULL,
	  { .init = &init_wildinc },
	},
	{ "wildmenu", "wmnu", "display completion matches in TUI",
	  OPT_BOOL, 0, NULL, &wildmenu_handler, NULL,
	  { .ref.bool_val = &cfg.wild_menu },
	},
	{ "wildstyle", "", "how to display completion matches",
	  OPT_ENUM, ARRAY_LEN(wildstyle_vals), wildstyle_vals, &wildstyle_handler,
	  NULL,
	  { .ref.enum_item = &cfg.wild_popup },
	},
	{ "wordchars", "", "chars considered to be part of a word",
	  OPT_STRLIST, 0, NULL, &wordchars_handler, NULL,
	  { .init = &init_wordchars },
	},
	{ "wrap", "", "wrap lines in preview",
	  OPT_BOOL, 0, NULL, &wrap_handler, NULL,
	  { .ref.bool_val = &cfg.wrap_quick_view },
	},
	{ "wrapscan", "ws", "wrap search around top/bottom",
	  OPT_BOOL, 0, NULL, &wrapscan_handler, NULL,
	  { .ref.bool_val = &cfg.wrap_scan },
	},

	/* Local options must be grouped here. */
	{ "dotfiles", "", "show dot files",
	  OPT_BOOL, 0, NULL, &dotfiles_global, &dotfiles_local,
	  { .init = &init_dotfiles },
	},
	{ "lsoptions", "", "settings for ls-like view",
	  OPT_STRLIST, ARRAY_LEN(lsoptions_enum), lsoptions_enum,
	  &lsoptions_global, &lsoptions_local,
	  { .init = &init_lsoptions },
	},
	{ "lsview", "", "display items in a grid",
	  OPT_BOOL, 0, NULL, &lsview_global, &lsview_local,
	  { .init = &init_lsview },
	},
	{ "milleroptions", "", "settings for miller view",
	  OPT_STRLIST, ARRAY_LEN(milleroptions_enum), milleroptions_enum,
	  &milleroptions_global, &milleroptions_local,
	  { .init = &init_milleroptions },
	},
	{ "millerview", "", "display cascading lists",
	  OPT_BOOL, 0, NULL, &millerview_global, &millerview_local,
	  { .init = &init_millerview },
	},
	{ "number", "nu", "display line numbers",
	  OPT_BOOL, 0, NULL, &number_global, &number_local,
	  { .init = &init_number },
	},
	{ "numberwidth", "nuw", "minimum width of line number column",
	  OPT_INT, 0, NULL, &numberwidth_global, &numberwidth_local,
	  { .init = &init_numberwidth },
	},
	{ "previewprg", "", "command to use to preview all files",
	  OPT_STR, 0, NULL, &previewprg_global, &previewprg_local,
	  { .ref.str_val = &empty },
	},
	{ "relativenumber", "rnu", "display relative line numbers",
	  OPT_BOOL, 0, NULL, &relativenumber_global, &relativenumber_local,
	  { .init = &init_relativenumber },
	},
	{ "sort", "", "sequence of sorting keys",
	  OPT_STRLIST, ARRAY_LEN(sort_types), sort_types, &sort_global, &sort_local,
	  { .init = &init_sort },
	},
	{ "sortgroups", "", "regexp for group sorting",
	  OPT_STR, 0, NULL, &sortgroups_global, &sortgroups_local,
	  { .ref.str_val = &empty },
	},
	{ "sortorder", "", "sorting direction of primary key",
	  OPT_ENUM, ARRAY_LEN(sortorder_enum), sortorder_enum, &sortorder_global,
	  &sortorder_local,
	  { .init = &init_sortorder },
	},
	{ "viewcolumns", "", "specification of view columns",
	  OPT_STRLIST, 0, NULL, &viewcolumns_global, &viewcolumns_local,
	  { .ref.str_val = &empty },
	},
};

static int error;

void
init_option_handlers(void)
{
	static int opt_changed;
	vle_opts_init(&opt_changed, &uni_handler);
	load_options_defaults();
	add_options();
}

/* Additional handler for processing of global-local options, namely for
 * updating inactive view(s). */
static void
uni_handler(const char name[], optval_t val, OPT_SCOPE scope)
{
	/* += and similar operators act in their way only for the current view, other
	 * views just get value resulted from the operation.  The behaviour is fine
	 * and doesn't seem to be a bug. */

	static size_t first_local = (size_t)-1;

	size_t i;

	if(!curr_stats.global_local_settings)
	{
		return;
	}

	/* Find first local option once. */
	if(first_local == (size_t)-1)
	{
		for(first_local = 0U; first_local < ARRAY_LEN(options); ++first_local)
		{
			if(options[first_local].local_handler != NULL)
			{
				break;
			}
		}
	}

	/* Look up option name and update it in the other view if found. */
	for(i = first_local; i < ARRAY_LEN(options); ++i)
	{
		if(strcmp(options[i].name, name) != 0)
		{
			continue;
		}

		view_t *const tmp_view = curr_view;

		/* Make sure option value remains valid even if updated in the handler.
		 * Do this before calling load_view_options(), because it can change the
		 * value. */
		if(ONE_OF(options[i].type, OPT_STR, OPT_STRLIST, OPT_CHARSET))
		{
			val.str_val = strdup(val.str_val);
		}

		int j;
		tab_info_t tab_info;
		for(j = 0; tabs_enum_all(j, &tab_info); ++j)
		{
			if(tab_info.view == curr_view)
			{
				continue;
			}

			curr_view = tab_info.view;
			/* XXX: do we actually need to load options?  We're calling handlers
			 *      directly anyway.  */
			load_view_options(curr_view);

			if(scope == OPT_LOCAL)
			{
				options[i].local_handler(OP_SET, val);
			}
			else
			{
				options[i].global_handler(OP_SET, val);
			}
		}

		if(ONE_OF(options[i].type, OPT_STR, OPT_STRLIST, OPT_CHARSET))
		{
			free(val.str_val);
		}

		curr_view = tmp_view;
		load_view_options(curr_view);
		break;
	}
}

/* Composes the default value for the 'classify' option. */
static void
init_classify(optval_t *val)
{
	val->str_val = (char *)classify_to_str();
	if(val->str_val == NULL)
	{
		val->str_val = "";
	}
}

const char *
classify_to_str(void)
{
	static char *classify;
	int ft, i;
	size_t len = 0;
	int memerr = 0;

	if(classify == NULL && (classify = strdup("")) == NULL)
	{
		return NULL;
	}

	classify[0] = '\0';

	/* Name-dependent decorations. */
	for(i = 0; i < cfg.name_dec_count && !memerr; ++i)
	{
		const file_dec_t *const name_dec = &cfg.name_decs[i];
		char *const doubled_commas_pat =
			double_char(matchers_get_expr(name_dec->matchers), ',');

		if(classify[0] != '\0')
		{
			memerr |= strappendch(&classify, &len, ',');
		}
		memerr |= strappend(&classify, &len, name_dec->prefix);
		memerr |= strappend(&classify, &len, "::");
		memerr |= strappend(&classify, &len, doubled_commas_pat);
		memerr |= strappend(&classify, &len, "::");
		memerr |= strappend(&classify, &len, name_dec->suffix);

		free(doubled_commas_pat);
	}

	/* Type-dependent decorations. */
	for(ft = 0; ft < FT_COUNT; ++ft)
	{
		const char *const prefix = cfg.type_decs[ft][DECORATION_PREFIX];
		const char *const suffix = cfg.type_decs[ft][DECORATION_SUFFIX];
		if(prefix[0] != '\0' || suffix[0] != '\0')
		{
			if(classify[0] != '\0')
			{
				memerr |= strappendch(&classify, &len, ',');
			}
			memerr |= strappend(&classify, &len, prefix);
			memerr |= strappend(&classify, &len, ":");
			memerr |= strappend(&classify, &len, get_type_str(ft));
			memerr |= strappend(&classify, &len, ":");
			memerr |= strappend(&classify, &len, suffix);
		}
	}

	return memerr ? NULL : classify;
}

/* Composes initial value for 'cpoptions' option from a set of configuration
 * flags. */
static void
init_cpoptions(optval_t *val)
{
	static char buf[32];
	/* TODO: move these flags to curr_stats structure, or not why would they fit
	 * there? */
	snprintf(buf, sizeof(buf), "%s%s%s",
			cfg.filter_inverted_by_default ? "f" : "",
			cfg.selection_is_primary       ? "s" : "",
			cfg.tab_switches_pane          ? "t" : "");
	val->str_val = buf;
}

/* Initializes 'dirsize' option from cfg.view_dir_size variable. */
static void
init_dirsize(optval_t *val)
{
	val->enum_item = (cfg.view_dir_size == VDS_SIZE ? 0 : 1);
}

/* Initializes 'fillchars' option. */
static void
init_fillchars(optval_t *val)
{
	static char buf[128];
	snprintf(buf, sizeof(buf), "hborder:%s,millersep:%s,vborder:%s",
			cfg.hborder_filler,
			cfg.millersep_filler,
			cfg.vborder_filler);
	val->str_val = buf;
}

/* Initializes 'hloptions' with a value built from current configuration. */
static void
init_hloptions(optval_t *val)
{
	static char buf[128];
	snprintf(buf, sizeof(buf), "filehi:%s",
			cfg.color_what == CW_PATH ? "path" :
			cfg.color_what == CW_ONE_ROW ? "onerow" : "allrows");
	val->str_val = buf;
}

/* Convenience function to format an endpoint inside the buffer, which should be
 * large enough.  Returns the buffer. */
static const char *
to_endpoint(int i, char buffer[])
{
	sprintf(buffer, "%d", i);
	return buffer;
}

static void
init_trashdir(optval_t *val)
{
	val->str_val = cfg.trash_dir;
}

/* Initializes 'dotfiles' option from global value. */
static void
init_dotfiles(optval_t *val)
{
	val->bool_val = !curr_view->hide_dot_g;
}

/* Initializes 'lsoptions' option from global value. */
static void
init_lsoptions(optval_t *val)
{
	fill_lsoptions(val, curr_view->ls_transposed_g, curr_view->ls_cols_g);
}

/* Initializes 'lsview' option from global value. */
static void
init_lsview(optval_t *val)
{
	val->bool_val = curr_view->ls_view_g;
}

/* Initializes 'milleroptions' option from global value. */
static void
init_milleroptions(optval_t *val)
{
	fill_milleroptions(val, curr_view->miller_ratios_g,
			curr_view->miller_preview_g);
}

/* Initializes 'millerview' option from global value. */
static void
init_millerview(optval_t *val)
{
	val->bool_val = curr_view->miller_view_g;
}

/* Initializes 'mouse' option from global state. */
static void
init_mouse(optval_t *val)
{
	static char buf[32];
	snprintf(buf, sizeof(buf), "%s%s%s%s%s%s",
			cfg.mouse & M_ALL_MODES ? "a" : "",
			cfg.mouse & M_CMDLINE_MODE ? "c" : "",
			cfg.mouse & M_MENU_MODE ? "m" : "",
			cfg.mouse & M_NORMAL_MODE ? "n" : "",
			cfg.mouse & M_VIEW_MODE ? "q" : "",
			cfg.mouse & M_VISUAL_MODE ? "v" : "");
	val->str_val = buf;
}

/* Initializes 'navoptions' option from global state. */
static void
init_navoptions(optval_t *val)
{
	static char buf[16];
	snprintf(buf, sizeof(buf), "open:%s", (cfg.nav_open_files ? "all" : "dirs"));

	val->str_val = buf;
}

/* Initializes 'previewoptions' option from global state. */
static void
init_previewoptions(optval_t *val)
{
	static char buf[128];

	size_t len = 0U;
	buf[0] = '\0';

	if(cfg.hard_graphics_clear)
	{
		(void)sstrappend(buf, &len, sizeof(buf), "hardgraphicsclear,");
	}
	if(cfg.top_tree_stats)
	{
		(void)sstrappend(buf, &len, sizeof(buf), "toptreestats,");
	}
	if(cfg.max_tree_depth > 0)
	{
		snprintf(buf + len, sizeof(buf) - len, "maxtreedepth:%d,",
				cfg.max_tree_depth);
	}
	if(cfg.graphics_delay != 0)
	{
		snprintf(buf + len, sizeof(buf) - len, "graphicsdelay:%d,",
				cfg.graphics_delay);
	}

	val->str_val = buf;
}

/* Initializes 'quickview' option from global state. */
static void
init_quickview(optval_t *val)
{
	val->bool_val = curr_stats.preview.on;
}

/* Initializes 'shortmess' from current configuration state. */
static void
init_shortmess(optval_t *val)
{
	static char buf[32];
	snprintf(buf, sizeof(buf), "%s%s%s%s",
			cfg.tail_tab_line_paths ? "L" : "",
			cfg.short_term_mux_titles ? "M" : "",
			cfg.trunc_normal_sb_msgs ? "T" : "",
			cfg.shorten_title_paths ? "p" : "");
	val->str_val = buf;
}

/* Initializes 'sizefmt' from current configuration state. */
static void
init_sizefmt(optval_t *val)
{
	*val = make_sizefmt_value();
}

/* Initializes value of 'iooptions' from configuration. */
static void
init_iooptions(optval_t *val)
{
	val->set_items = (cfg.fast_file_cloning != 0) << 0
	               | (cfg.data_sync         != 0) << 1;
}

/* Default-initializes whether to display file numbers. */
static void
init_number(optval_t *val)
{
	val->bool_val = 0;
}

/* Default-initializes minimum width of file number field. */
static void
init_numberwidth(optval_t *val)
{
	val->int_val = 4;
}

/* Default-initializes whether to display relative file numbers. */
static void
init_relativenumber(optval_t *val)
{
	val->bool_val = 0;
}

static void
init_sort(optval_t *val)
{
	val->str_val = "+name";
}

static void
init_sortorder(optval_t *val)
{
	val->enum_item = 0;
}

/* Composes initial value for 'tuioptions' option from a set of configuration
 * flags. */
static void
init_tuioptions(optval_t *val)
{
	static char buf[32];
	snprintf(buf, sizeof(buf), "%s%s%s%s%s%s",
			cfg.ellipsis_position < 0 ? "l" : "",
			cfg.extra_padding ? "p" : "",
			cfg.ellipsis_position > 0 ? "r" : "",
			cfg.side_borders_visible ? "s" : "",
			cfg.use_unicode_characters ? "u" : "",
			cfg.flexible_splitter ? "v" : "");
	val->str_val = buf;
}

/* Composes initial value of 'uioptions' from current configuration. */
static void
init_uioptions(optval_t *val)
{
	val->set_items = (cfg.always_show_io_details != 0) << 0;
}

/* Composes initial value of 'wildinc' from current configuration. */
static void
init_wildinc(optval_t *val)
{
	val->str_val = (char *)matcher_get_undec(cfg.wild_inc);
}

/* Formats 'wordchars' initial value from cfg.word_chars array. */
static void
init_wordchars(optval_t *val)
{
	static char *str;

	size_t i;
	size_t len;

	/* This is for the case when cfg.word_chars is empty, so that we won't return
	 * NULL as initial value. */
	replace_string(&str, "");

	len = 0U;
	i = 0U;
	while(i < sizeof(cfg.word_chars))
	{
		int l, r;

		while(i < sizeof(cfg.word_chars) && !cfg.word_chars[i])
		{
			++i;
		}
		l = i;
		while(i < sizeof(cfg.word_chars) && cfg.word_chars[i])
		{
			++i;
		}
		r = i - 1;

		if((size_t)l < sizeof(cfg.word_chars))
		{
			char left[16];
			char range[32];

			if(len != 0U)
			{
				(void)strappendch(&str, &len, ',');
			}

			if(r == l)
			{
				copy_str(range, sizeof(range), to_endpoint(l, left));
			}
			else
			{
				char right[16];
				snprintf(range, sizeof(range), "%s-%s", to_endpoint(l, left),
						to_endpoint(r, right));
			}
			(void)strappend(&str, &len, range);
		}
	}

	val->str_val = str;
}

static void
load_options_defaults(void)
{
	size_t i;
	for(i = 0U; i < ARRAY_LEN(options); ++i)
	{
		if(options[i].initializer.init != NULL)
		{
			options[i].initializer.init(&options[i].val);
		}
		else if(ONE_OF(options[i].type, OPT_STR, OPT_STRLIST, OPT_CHARSET))
		{
			options[i].val.str_val = *options[i].initializer.ref.str_val;
		}
		else
		{
			options[i].val.int_val = *options[i].initializer.ref.int_val;
		}
	}
}

/* Registers options in options unit. */
static void
add_options(void)
{
	size_t i;
	for(i = 0U; i < ARRAY_LEN(options); ++i)
	{
		struct opt_data_t *const opt = &options[i];

		vle_opts_add(opt->name, opt->abbr, opt->descr, opt->type, OPT_GLOBAL,
				opt->val_count, opt->vals, opt->global_handler, opt->val);

		if(opt->local_handler != NULL)
		{
			vle_opts_add(opt->name, opt->abbr, opt->descr, opt->type, OPT_LOCAL,
					opt->val_count, opt->vals, opt->local_handler, opt->val);
		}
	}
}

void
reset_local_options(view_t *view)
{
	optval_t val;

	memcpy(view->sort, view->sort_g, sizeof(view->sort));
	load_sort_option_inner(view, view->sort);

	view->hide_dot = view->hide_dot_g;
	val.int_val = !view->hide_dot_g;
	vle_opts_assign("dotfiles", val, OPT_LOCAL);

	view->ls_transposed = view->ls_transposed_g;
	fill_lsoptions(&val, view->ls_transposed_g, view->ls_cols_g);
	vle_opts_assign("lsoptions", val, OPT_LOCAL);

	fview_set_lsview(view, view->ls_view_g);
	val.int_val = view->ls_view_g;
	vle_opts_assign("lsview", val, OPT_LOCAL);

	view->miller_preview = view->miller_preview_g;
	memcpy(view->miller_ratios, view->miller_ratios_g,
			sizeof(view->miller_ratios));
	fill_milleroptions(&val, view->miller_ratios_g, view->miller_preview_g);
	vle_opts_assign("milleroptions", val, OPT_LOCAL);

	fview_set_millerview(view, view->miller_view_g);
	val.int_val = view->miller_view_g;
	vle_opts_assign("millerview", val, OPT_LOCAL);

	view->num_type = view->num_type_g;
	val.bool_val = view->num_type_g & NT_SEQ;
	vle_opts_assign("number", val, OPT_LOCAL);
	val.bool_val = view->num_type_g & NT_REL;
	vle_opts_assign("relativenumber", val, OPT_LOCAL);

	view->num_width = view->num_width_g;
	val.int_val = view->num_width_g;
	vle_opts_assign("numberwidth", val, OPT_LOCAL);

	replace_string(&view->view_columns, view->view_columns_g);
	set_viewcolumns(view, view->view_columns);
	val.str_val = view->view_columns;
	vle_opts_assign("viewcolumns", val, OPT_LOCAL);

	replace_string(&view->sort_groups, view->sort_groups_g);
	val.str_val = view->sort_groups;
	vle_opts_assign("sortgroups", val, OPT_LOCAL);

	replace_string(&view->preview_prg, view->preview_prg_g);
	val.str_val = view->preview_prg;
	vle_opts_assign("previewprg", val, OPT_LOCAL);
}

void
load_view_options(view_t *view)
{
	optval_t val;

	load_sort_option(view);

	val.str_val = view->view_columns;
	vle_opts_assign("viewcolumns", val, OPT_LOCAL);
	val.str_val = view->view_columns_g;
	vle_opts_assign("viewcolumns", val, OPT_GLOBAL);

	val.str_val = view->sort_groups;
	vle_opts_assign("sortgroups", val, OPT_LOCAL);
	val.str_val = view->sort_groups_g;
	vle_opts_assign("sortgroups", val, OPT_GLOBAL);

	val.bool_val = !view->hide_dot;
	vle_opts_assign("dotfiles", val, OPT_LOCAL);
	val.bool_val = !view->hide_dot_g;
	vle_opts_assign("dotfiles", val, OPT_GLOBAL);

	fill_lsoptions(&val, view->ls_transposed, view->ls_cols);
	vle_opts_assign("lsoptions", val, OPT_LOCAL);
	fill_lsoptions(&val, view->ls_transposed_g, view->ls_cols_g);
	vle_opts_assign("lsoptions", val, OPT_GLOBAL);

	val.bool_val = view->ls_view;
	vle_opts_assign("lsview", val, OPT_LOCAL);
	val.bool_val = view->ls_view_g;
	vle_opts_assign("lsview", val, OPT_GLOBAL);

	fill_milleroptions(&val, view->miller_ratios, view->miller_preview);
	vle_opts_assign("milleroptions", val, OPT_LOCAL);
	fill_milleroptions(&val, view->miller_ratios_g, view->miller_preview_g);
	vle_opts_assign("milleroptions", val, OPT_GLOBAL);

	val.bool_val = view->miller_view;
	vle_opts_assign("millerview", val, OPT_LOCAL);
	val.bool_val = view->miller_view_g;
	vle_opts_assign("millerview", val, OPT_GLOBAL);

	val.bool_val = view->num_type & NT_SEQ;
	vle_opts_assign("number", val, OPT_LOCAL);
	val.bool_val = view->num_type_g & NT_SEQ;
	vle_opts_assign("number", val, OPT_GLOBAL);

	val.int_val = view->num_width;
	vle_opts_assign("numberwidth", val, OPT_LOCAL);
	val.int_val = view->num_width_g;
	vle_opts_assign("numberwidth", val, OPT_GLOBAL);

	val.bool_val = view->num_type & NT_REL;
	vle_opts_assign("relativenumber", val, OPT_LOCAL);
	val.bool_val = view->num_type_g & NT_REL;
	vle_opts_assign("relativenumber", val, OPT_GLOBAL);

	val.str_val = view->preview_prg;
	vle_opts_assign("previewprg", val, OPT_LOCAL);
	val.str_val = view->preview_prg_g;
	vle_opts_assign("previewprg", val, OPT_GLOBAL);
}

void
clone_local_options(const view_t *from, view_t *to, int defer_slow)
{
	replace_string(&to->view_columns, from->view_columns);
	replace_string(&to->view_columns_g, from->view_columns_g);

	to->num_width = from->num_width;
	to->num_width_g = from->num_width_g;

	to->num_type = from->num_type;
	to->num_type_g = from->num_type_g;

	to->hide_dot = from->hide_dot;
	to->hide_dot_g = from->hide_dot_g;

	replace_string(&to->sort_groups, from->sort_groups);
	replace_string(&to->sort_groups_g, from->sort_groups_g);

	const signed char *sort =
	  (flist_custom_active(from) && ui_view_unsorted(from)) ? from->custom.sort
	                                                        : from->sort;
	memcpy(to->sort, sort, sizeof(to->sort));
	memcpy(to->sort_g, from->sort_g, sizeof(to->sort_g));
	sorting_changed(to, defer_slow);

	to->ls_transposed_g = from->ls_transposed_g;

	to->ls_view_g = from->ls_view_g;
	fview_set_lsview(to, from->ls_view);

	memcpy(to->miller_ratios_g, from->miller_ratios_g,
			sizeof(to->miller_ratios_g));
	memcpy(to->miller_ratios, from->miller_ratios, sizeof(to->miller_ratios));

	to->miller_view_g = from->miller_view_g;
	fview_set_millerview(to, from->miller_view);

	to->miller_preview_g = from->miller_preview_g;
	to->miller_preview = from->miller_preview;

	replace_string(&to->preview_prg, from->preview_prg);
	replace_string(&to->preview_prg_g, from->preview_prg_g);
}

void
load_sort_option(view_t *view)
{
	load_sort_option_inner(view, view->sort);
	load_sort_option_inner(view, view->sort_g);

	/* The check is to skip this in tests which don't need columns. */
	if(view->columns != NULL)
	{
		if(!cv_compare(view->custom.type))
		{
			set_viewcolumns(view, view->view_columns);
		}
	}
}

/* Loads sorting related options ("sort" and "sortorder"). */
static void
load_sort_option_inner(view_t *view, signed char sort_keys[])
{
	/* This approximate maximum length also includes "+" or "-" sign and a
	 * comma (",") between items. */
	enum { MAX_SORT_KEY_LEN = 16 };

	int i;

	optval_t val;
	char opt_val[MAX_SORT_KEY_LEN*SK_COUNT];
	size_t opt_val_len = 0U;
	OPT_SCOPE scope = (sort_keys == view->sort) ? OPT_LOCAL : OPT_GLOBAL;

	if(sort_keys == view->custom.sort)
	{
		val.str_val = "";
		vle_opts_assign("sort", val, OPT_LOCAL);
		return;
	}

	opt_val[0] = '\0';

	ui_view_sort_list_ensure_well_formed(view, sort_keys);

	/* Produce a string, which represents a list of sorting keys. */
	i = -1;
	while(++i < SK_COUNT && abs(sort_keys[i]) <= SK_LAST)
	{
		const int sort_option = sort_keys[i];
		const char *const comma = (opt_val_len == 0U) ? "" : ",";
		const char option_mark = (sort_option < 0) ? '-' : '+';
		const char *const option_name = sort_enum[abs(sort_option)];

		snprintf(opt_val + opt_val_len, sizeof(opt_val) - opt_val_len, "%s%c%s",
				comma, option_mark, option_name);
		opt_val_len += strlen(opt_val + opt_val_len);
	}

	val.str_val = opt_val;
	vle_opts_assign("sort", val, scope);

	val.enum_item = (sort_keys[0] < 0);
	vle_opts_assign("sortorder", val, scope);
}

int
process_set_args(const char args[], int global, int local)
{
	int set_options_error;
	const char *text_buffer;
	OPT_SCOPE scope;

	assert((global || local) && "At least one type of options must be changed.");

	vle_tb_clear(vle_err);

	/* Call of vle_opts_set() can change error. */
	error = 0;
	if(local && global)
	{
		scope = OPT_ANY;
	}
	else
	{
		scope = local ? OPT_LOCAL : OPT_GLOBAL;
	}
	set_options_error = (vle_opts_set(args, scope) != 0);
	error |= set_options_error;
	text_buffer = vle_tb_get_data(vle_err);

	if(error)
	{
		vle_tb_append_line(vle_err, "Invalid argument for :set command");
		/* We just modified text buffer and can't use text_buffer variable. */
		ui_sb_err(vle_tb_get_data(vle_err));
	}
	else if(text_buffer[0] != '\0')
	{
		ui_sb_msg(text_buffer);
	}

	return error ? -1 : (text_buffer[0] != '\0');
}

static void
aproposprg_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.apropos_prg, val.str_val);
}

/* Handles changes of 'autocd' option. */
static void
autocd_handler(OPT_OP op, optval_t val)
{
	cfg.auto_cd = val.bool_val;
}

static void
autochpos_handler(OPT_OP op, optval_t val)
{
	cfg.auto_ch_pos = val.bool_val;
	if(curr_stats.load_stage < 2)
	{
		flist_hist_clear(curr_view);
		flist_hist_clear(other_view);
	}
}

/* Handles changes of 'caseoptions' option.  Updates configuration and
 * normalizes option value. */
static void
caseoptions_handler(OPT_OP op, optval_t val)
{
	char valid_val[32];
	const char *p;

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

	p = val.str_val;
	while(*p != '\0')
	{
		if(*p == 'P' || *p == 'p')
		{
			cfg.case_override |= CO_PATH_COMPL;
			cfg.case_ignore = (*p == 'p')
			                ? (cfg.case_ignore | CO_PATH_COMPL)
			                : (cfg.case_ignore & ~CO_PATH_COMPL);
		}
		else if(*p == 'g' || *p == 'G')
		{
			cfg.case_override |= CO_GOTO_FILE;
			cfg.case_ignore = (*p == 'g')
			                ? (cfg.case_ignore | CO_GOTO_FILE)
			                : (cfg.case_ignore & ~CO_GOTO_FILE);
		}

		++p;
	}

	valid_val[0] = '\0';
	if(cfg.case_override & CO_PATH_COMPL)
	{
		strcat(valid_val, (cfg.case_ignore & CO_PATH_COMPL) ? "p" : "P");
	}
	if(cfg.case_override & CO_GOTO_FILE)
	{
		strcat(valid_val, (cfg.case_ignore & CO_GOTO_FILE) ? "g" : "G");
	}
	val.str_val = valid_val;

	vle_opts_assign("caseoptions", val, OPT_GLOBAL);
}

/* Specifies directories to check on cding by relative path. */
static void
cdpath_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.cd_path, val.str_val);
}

/* Whether directory path should always be resolved to real path (no symbolic
 * links). */
static void
chaselinks_handler(OPT_OP op, optval_t val)
{
	cfg.chase_links = val.bool_val;

	/* Trigger paths update. */
	(void)change_directory(other_view, other_view->curr_dir);
	(void)change_directory(curr_view, curr_view->curr_dir);
	ui_views_update_titles();
}

static void
classify_handler(OPT_OP op, optval_t val)
{
	char type_decs[FT_COUNT][2][9] = {};

	if(str_to_classify(val.str_val, type_decs) == 0)
	{
		assert(sizeof(cfg.type_decs) == sizeof(type_decs) && "Arrays diverged");
		memcpy(&cfg.type_decs, &type_decs, sizeof(cfg.type_decs));

		int i;
		tab_info_t tab_info;
		for(i = 0; tabs_enum_all(i, &tab_info); ++i)
		{
			ui_view_reset_decor_cache(tab_info.view);
			fview_decors_updated(tab_info.view);
			ui_view_schedule_redraw(tab_info.view);
		}
	}
	else
	{
		error = 1;
	}

	init_classify(&val);
	vle_opts_assign("classify", val, OPT_GLOBAL);
}

/* Fills the decorations array with parsed classification values from the str.
 * It's assumed that decorations array is zeroed.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
str_to_classify(const char str[], char type_decs[FT_COUNT][2][9])
{
	char *saveptr;
	char *str_copy;
	char *token;
	int error_encountered;
	file_dec_t *name_decs = NULL;
	DA_INSTANCE(name_decs);

	str_copy = strdup(str);
	if(str_copy == NULL)
	{
		/* Not enough memory. */
		return 1;
	}

	error_encountered = 0;
	saveptr = NULL;
	for(token = str_copy; (token = split_and_get_dc(token, &saveptr));)
	{
		FileType type = FT_UNK;
		const char *expr = NULL;
		const char *suffix = pick_out_decoration(token, &type, &expr);

		if(suffix == NULL)
		{
			vle_tb_append_linef(vle_err, "Invalid filetype: %s", token);
			error_encountered = 1;
		}
		else if(expr != NULL)
		{
			char *error;
			matchers_t *const ms = matchers_alloc(expr, 0, 1, "", &error);
			if(ms == NULL)
			{
				vle_tb_append_linef(vle_err, "Wrong pattern (%s): %s", expr, error);
				free(error);
				error_encountered = 1;
			}
			else
			{
				file_dec_t *const name_dec = DA_EXTEND(name_decs);
				if(name_dec == NULL)
				{
					vle_tb_append_line(vle_err, "Not enough memory");
					error_encountered = 1;
				}
				else
				{
					name_dec->matchers = ms;
				}

				error_encountered |= validate_decorations(token, suffix);

				if(!error_encountered)
				{
					DA_COMMIT(name_decs);
					strcpy(name_dec->prefix, token);
					strcpy(name_dec->suffix, suffix);
				}
				else
				{
					matchers_free(ms);
				}
			}
		}
		else
		{
			error_encountered |= validate_decorations(token, suffix);

			if(!error_encountered)
			{
				strcpy(type_decs[type][DECORATION_PREFIX], token);
				strcpy(type_decs[type][DECORATION_SUFFIX], suffix);
			}
		}
	}
	free(str_copy);

	if(error_encountered)
	{
		free_file_decs(name_decs, DA_SIZE(name_decs));
	}
	else
	{
		free_file_decs(cfg.name_decs, cfg.name_dec_count);
		cfg.name_decs = name_decs;
		cfg.name_dec_count = DA_SIZE(name_decs);
	}

	return error_encountered;
}

/* Puts '\0' after prefix end and returns pointer to the suffix beginning or
 * NULL on unknown filetype specifier.  Sets *expr if this is a name-dependent
 * specification. */
static const char *
pick_out_decoration(char classify_item[], FileType *type, const char **expr)
{
	char *left, *right;

	int filetype;
	for(filetype = 0; filetype < FT_COUNT; ++filetype)
	{
		/* Below is a faster version of (pick_out_decoration can be invoked a lot):
		 * len = snprintf(name, sizeof(name), ":%s:", get_type_str(filetype)); */
		char name[16];
		name[0] = ':';
		int len = copy_str(name + 1, sizeof(name) - 2, get_type_str(filetype));
		name[len++] = ':';
		name[len] = '\0';

		char *item_name = strstr(classify_item, name);
		if(item_name != NULL)
		{
			*type = filetype;
			item_name[0] = '\0';
			return &item_name[len];
		}
	}

	left = strstr(classify_item, "::");
	right = (left == NULL) ? NULL : strstr(left + 2, "::");
	if(right != NULL)
	{
		*left = '\0';
		*right = '\0';
		*expr = left + 2;
		return right + 2;
	}

	return NULL;
}

/* Checks whether decorations are well-formatted and prints errors if they
 * aren't.  Returns zero if everything is OK, otherwise non-zero is returned. */
static int
validate_decorations(const char prefix[], const char suffix[])
{
	int error = 0;
	if(strlen(prefix) > 8)
	{
		vle_tb_append_linef(vle_err, "Too long prefix: %s", prefix);
		error = 1;
	}
	if(strlen(suffix) > 8)
	{
		vle_tb_append_linef(vle_err, "Too long suffix: %s", suffix);
		error = 1;
	}
	return error;
}

/* Frees array of file declarations of length count (both elements and array
 * itself). */
static void
free_file_decs(file_dec_t *name_decs, int count)
{
	int i;
	for(i = 0; i < count; ++i)
	{
		matchers_free(name_decs[i].matchers);
	}
	free(name_decs);
}

/* Handles updates of the global 'columns' option, which reflects width of
 * terminal. */
static void
columns_handler(OPT_OP op, optval_t val)
{
	/* Handle case when 'columns' value wasn't yet initialized. */
	if(val.int_val == INT_MIN)
	{
		val.int_val = curr_stats.initial_columns;
		if(val.int_val == INT_MIN)
		{
			val.int_val = getmaxx(stdscr);
		}
	}

	if(val.int_val < MIN_TERM_WIDTH)
	{
		val.int_val = MIN_TERM_WIDTH;
		vle_tb_append_linef(vle_err, "At least %d columns needed", MIN_TERM_WIDTH);
		error = 1;
	}

	if(cfg.columns != val.int_val)
	{
		resize_term(getmaxy(stdscr), val.int_val);
		update_screen(UT_REDRAW);
		cfg.columns = getmaxx(stdscr);
	}

	/* Need to update value of option in case it was corrected above. */
	val.int_val = cfg.columns;
	vle_opts_assign("columns", val, OPT_GLOBAL);
}

static void
confirm_handler(OPT_OP op, optval_t val)
{
	cfg.confirm = val.set_items;
}

/* Parses set of compatibility flags and changes configuration accordingly. */
static void
cpoptions_handler(OPT_OP op, optval_t val)
{
	const char *p;

	/* Set all options to new kind of behaviour. */
	cfg.filter_inverted_by_default = 0;
	cfg.selection_is_primary = 0;
	cfg.tab_switches_pane = 0;

	/* Reset behaviour to compatibility mode for each flag listed in the value. */
	p = val.str_val;
	while(*p != '\0')
	{
		switch(*p)
		{
			case 'f':
				cfg.filter_inverted_by_default = 1;
				break;
			case 's':
				cfg.selection_is_primary = 1;
				break;
			case 't':
				cfg.tab_switches_pane = 1;
				break;

			default:
				assert(0 && "Unhandled cpoptions flag.");
				break;
		}
		++p;
	}
}

/* Assigns new value for 'cvoptions'. */
static void
cvoptions_handler(OPT_OP op, optval_t val)
{
	cfg.cvoptions = val.set_items;
}

/* Handles updates of the 'deleteprg' option. */
static void
deleteprg_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.delete_prg, val.str_val);
}

/* Handles changes to the way directory size is displayed in a view. */
static void
dirsize_handler(OPT_OP op, optval_t val)
{
	cfg.view_dir_size = val.enum_item;
	update_screen(UT_REDRAW);
}

static void
dotdirs_handler(OPT_OP op, optval_t val)
{
	cfg.dot_dirs = val.set_items;
	update_screen(UT_FULL);
}

static void
fastrun_handler(OPT_OP op, optval_t val)
{
	cfg.fast_run = val.bool_val;
}

/* Handles new value for 'fillchars' option. */
static void
fillchars_handler(OPT_OP op, optval_t val)
{
	char *new_val = strdup(val.str_val);
	if(new_val == NULL)
	{
		load_fillchars();
		return;
	}

	char *part = new_val, *state = NULL;

	/* Save current state and load default values. */
	char *const old_vborder = cfg.vborder_filler;
	char *const old_hborder = cfg.hborder_filler;
	char *const old_millersep = cfg.millersep_filler;
	cfg.vborder_filler = strdup(" ");
	cfg.hborder_filler = strdup("");
	cfg.millersep_filler = strdup("");

	if(cfg.vborder_filler == NULL || cfg.hborder_filler == NULL ||
			cfg.millersep_filler == NULL)
	{
		free(new_val);
		put_string(&cfg.vborder_filler, old_vborder);
		put_string(&cfg.hborder_filler, old_hborder);
		put_string(&cfg.millersep_filler, old_millersep);
		load_fillchars();
		return;
	}

	int error = 0;
	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(starts_with_lit(part, "vborder:"))
		{
			(void)replace_string(&cfg.vborder_filler, after_first(part, ':'));
		}
		else if(starts_with_lit(part, "hborder:"))
		{
			(void)replace_string(&cfg.hborder_filler, after_first(part, ':'));
		}
		else if(starts_with_lit(part, "millersep:"))
		{
			(void)replace_string(&cfg.millersep_filler, after_first(part, ':'));
		}
		else
		{
			error = 1;
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'fillchars' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(error)
	{
		/* Restore previous state on error. */
		put_string(&cfg.vborder_filler, old_vborder);
		put_string(&cfg.hborder_filler, old_hborder);
		put_string(&cfg.millersep_filler, old_millersep);
	}
	else
	{
		/* Schedule a redraw since option was set successfully. */
		stats_redraw_later();

		/* Drop saved state. */
		free(old_vborder);
		free(old_hborder);
		free(old_millersep);
	}

	/* In case of error, restore previous value, otherwise reload it anyway to
	 * remove any duplicates. */
	load_fillchars();
}

/* Sets value of 'fillchars' option by composing it from current
 * configuration. */
static void
load_fillchars(void)
{
	optval_t val;
	init_fillchars(&val);
	vle_opts_assign("fillchars", val, OPT_GLOBAL);
}

static void
findprg_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.find_prg, val.str_val);
}

static void
followlinks_handler(OPT_OP op, optval_t val)
{
	cfg.follow_links = val.bool_val;
}

static void
fusehome_handler(OPT_OP op, optval_t val)
{
	char *const expanded_path = expand_path(val.str_val);
	/* Set cfg.fuse_home in the correct way. */
	if(cfg_set_fuse_home(expanded_path) != 0)
	{
		/* Reset the 'fusehome' options to its previous value. */
		val.str_val = cfg.fuse_home;
		vle_opts_assign("fusehome", val, OPT_GLOBAL);
	}
	free(expanded_path);
}

static void
gdefault_handler(OPT_OP op, optval_t val)
{
	cfg.gdefault = val.bool_val;
}

static void
grepprg_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.grep_prg, val.str_val);
}

/* Handles changes of 'histcursor'.  Updates related configuration value. */
static void
histcursor_handler(OPT_OP op, optval_t val)
{
	cfg.ch_pos_on = val.set_items;
}

static void
history_handler(OPT_OP op, optval_t val)
{
	cfg_resize_histories(val.int_val);
}

/* Handles changes of 'hloptions'.  Updates related configuration value. */
static void
hloptions_handler(OPT_OP op, optval_t val)
{
	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	ColorWhat color_what = CW_ONE_ROW;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		const char *option = part;
		if(skip_prefix(&option, "filehi:"))
		{
			if(strcmp(option, "path") == 0)
			{
				color_what = CW_PATH;
			}
			else if(strcmp(option, "onerow") == 0)
			{
				color_what = CW_ONE_ROW;
			}
			else if(strcmp(option, "allrows") == 0)
			{
				color_what = CW_ALL_ROWS;
			}
			else
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"filehi\" value: %s",
						option);
				break;
			}
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'hloptions' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		cfg.color_what = color_what;
		stats_redraw_later();
	}

	init_hloptions(&val);
	vle_opts_assign("hloptions", val, OPT_GLOBAL);
}

static void
hlsearch_handler(OPT_OP op, optval_t val)
{
	cfg.hl_search = val.bool_val;

	/* Reset remembered search results, so that n/N trigger search repetition and
	 * selection/unselection of elements. */
	reset_search_results(curr_view);
	reset_search_results(other_view);
}

static void
iec_handler(OPT_OP op, optval_t val)
{
	cfg.sizefmt.ieci_prefixes = val.bool_val;

	redraw_lists();
}

static void
ignorecase_handler(OPT_OP op, optval_t val)
{
	cfg.ignore_case = val.bool_val;
}

static void
incsearch_handler(OPT_OP op, optval_t val)
{
	cfg.inc_search = val.bool_val;
}

/* Handles changes of 'iooptions'.  Updates related configuration values. */
static void
iooptions_handler(OPT_OP op, optval_t val)
{
	cfg.fast_file_cloning = ((val.set_items & 1) != 0);
	cfg.data_sync = ((val.set_items & 2) != 0);
}

static void
laststatus_handler(OPT_OP op, optval_t val)
{
	cfg.display_statusline = val.bool_val;
	if(cfg.display_statusline)
	{
		if(curr_stats.split == VSPLIT)
		{
			scroll_line_down(&lwin);
		}
		scroll_line_down(&rwin);
	}

	stats_redraw_later();
}

/* Handles updates of the global 'lines' option, which reflects height of
 * terminal. */
static void
lines_handler(OPT_OP op, optval_t val)
{
	/* Handle case when 'lines' value wasn't yet initialized. */
	if(val.int_val == INT_MIN)
	{
		val.int_val = curr_stats.initial_lines;
		if(val.int_val == INT_MIN)
		{
			val.int_val = getmaxy(stdscr);
		}
	}

	if(val.int_val < MIN_TERM_HEIGHT)
	{
		val.int_val = MIN_TERM_HEIGHT;
		vle_tb_append_linef(vle_err, "At least %d lines needed", MIN_TERM_HEIGHT);
		error = 1;
	}

	if(cfg.lines != val.int_val)
	{
		LOG_INFO_MSG("resize_term(%d, %d)", val.int_val, getmaxx(stdscr));
		resize_term(val.int_val, getmaxx(stdscr));
		update_screen(UT_REDRAW);
		cfg.lines = getmaxy(stdscr);
	}

	/* Need to update value of option in case it was corrected above. */
	val.int_val = cfg.lines;
	vle_opts_assign("lines", val, OPT_GLOBAL);
}

/* Handles updates of the 'locateprg' option. */
static void
locateprg_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.locate_prg, val.str_val);
}

#ifndef _WIN32

/* Handles updates of the 'mediaprg' option. */
static void
mediaprg_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.media_prg, val.str_val);
}

#endif

/* Minimum period on waiting for the input.  Works together with timeoutlen. */
static void
mintimeoutlen_handler(OPT_OP op, optval_t val)
{
	if(val.int_val <= 0)
	{
		vle_tb_append_linef(vle_err, "Argument must be > 0: %d", val.int_val);
		error = 1;
		val.int_val = 1;
		vle_opts_assign("mintimeoutlen", val, OPT_GLOBAL);
		return;
	}

	cfg.min_timeout_len = val.int_val;
}

static void
scroll_line_down(view_t *view)
{
	--view->window_rows;
	if(view->list_pos == view->top_line + view->window_rows)
	{
		++view->top_line;
		--view->curr_line;
		draw_dir_list(view);
	}
	wresize(view->win, view->window_rows, view->window_cols);
}

/* Handles updates of the 'mouse' option. */
static void
mouse_handler(OPT_OP op, optval_t val)
{
	int mouse = 0;

	const char *p;
	for(p = val.str_val; *p != '\0'; ++p)
	{
		switch(*p)
		{
			case 'a': mouse |= M_ALL_MODES; break;
			case 'c': mouse |= M_CMDLINE_MODE; break;
			case 'm': mouse |= M_MENU_MODE; break;
			case 'n': mouse |= M_NORMAL_MODE; break;
			case 'q': mouse |= M_VIEW_MODE; break;
			case 'v': mouse |= M_VISUAL_MODE; break;

			default:
				assert(0 && "Unhandled mouse flag.");
				break;
		}
	}

	/* Update terminal state only if there is a need for it. */
	if((cfg.mouse == 0) != (mouse == 0))
	{
		ui_set_mouse_active(mouse != 0);
	}

	cfg.mouse = mouse;
}

/* Handles updates of the 'navoptions' option. */
static void
navoptions_handler(OPT_OP op, optval_t val)
{
	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	int open_all = 0;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(starts_with_lit(part, "open:"))
		{
			const char *const str = after_first(part, ':');
			if(strcmp(str, "all") == 0)
			{
				open_all = 1;
			}
			else if(strcmp(str, "dirs") == 0)
			{
				open_all = 0;
			}
			else
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"open\" value: %s", str);
				break;
			}
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'navoptions' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		cfg.nav_open_files = open_all;
	}
}

/* Handles updates of the 'previewoptions' option. */
static void
previewoptions_handler(OPT_OP op, optval_t val)
{
	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	int graphics_delay = 0;
	int hard_graphics_clear = 0;
	int top_tree_stats = 0;
	int max_tree_depth = 0;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(starts_with_lit(part, "graphicsdelay:"))
		{
			const char *const num = after_first(part, ':');
			if(!read_int(num, &graphics_delay))
			{
				vle_tb_append_linef(vle_err,
						"Failed to parse \"graphicsdelay\" value: %s", num);
				break;
			}
			if(graphics_delay < 0)
			{
				vle_tb_append_linef(vle_err,
						"\"graphicsdelay\" can't be negative, got: %s", num);
				break;
			}
		}
		else if(starts_with_lit(part, "maxtreedepth:"))
		{
			const char *const num = after_first(part, ':');
			if(!read_int(num, &max_tree_depth))
			{
				vle_tb_append_linef(vle_err,
						"Failed to parse \"maxtreedepth\" value: %s", num);
				break;
			}
			if(max_tree_depth < 0)
			{
				vle_tb_append_linef(vle_err,
						"\"maxtreedepth\" can't be negative, got: %s", num);
				break;
			}
		}
		else if(strcmp(part, "hardgraphicsclear") == 0)
		{
			hard_graphics_clear = 1;
		}
		else if(strcmp(part, "toptreestats") == 0)
		{
			top_tree_stats = 1;
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err,
					"Unknown key for 'previewoptions' option: %s", part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		int need_update = (top_tree_stats != cfg.top_tree_stats)
		               || (max_tree_depth != cfg.max_tree_depth);

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

		if(need_update)
		{
			text_option_changed();
		}
	}

	/* In case of error, restore previous value, otherwise reload it anyway to
	 * remove any duplicates. */
	init_previewoptions(&val);
	vle_opts_assign("previewoptions", val, OPT_GLOBAL);
}

/* Handles switch that controls visibility of quick view. */
static void
quickview_handler(OPT_OP op, optval_t val)
{
	curr_stats.preview.on = val.bool_val;
	stats_redraw_later();
}

static void
rulerformat_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.ruler_format, val.str_val);
}

static void
runexec_handler(OPT_OP op, optval_t val)
{
	cfg.auto_execute = val.bool_val;
}

static void
scrollbind_handler(OPT_OP op, optval_t val)
{
	cfg.scroll_bind = val.bool_val;
	ui_remember_scroll_offset();
}

static void
scrolloff_handler(OPT_OP op, optval_t val)
{
	if(val.int_val < 0)
	{
		vle_tb_append_linef(vle_err, "Invalid scroll size: %d", val.int_val);
		error = 1;
		vle_opts_restore_default("scrolloff", OPT_GLOBAL);
		return;
	}

	cfg.scroll_off = val.int_val;
	if(cfg.scroll_off > 0)
	{
		fview_cursor_redraw(curr_view);
	}
}

/* Handles changes of 'shell' option which specifies shell interpreter
 * command. */
static void
shell_handler(OPT_OP op, optval_t val)
{
	cfg_set_shell(val.str_val);
}

/* Handles changes of 'shellcmdflag' option which specifies shell interpreter
 * command-line option that enables processing of the next argument as a
 * command. */
static void
shellcmdflag_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.shell_cmd_flag, val.str_val);
}

/* Handles changes of 'shortmess' option by expanding flags into actual option
 * values. */
static void
shortmess_handler(OPT_OP op, optval_t val)
{
	cfg.tail_tab_line_paths = 0;
	cfg.trunc_normal_sb_msgs = 0;
	cfg.shorten_title_paths = 0;
	cfg.short_term_mux_titles = 0;

	const char *p = val.str_val;
	while(*p != '\0')
	{
		switch(*p)
		{
			case 'L': cfg.tail_tab_line_paths = 1; break;
			case 'M': cfg.short_term_mux_titles = 1; break;
			case 'T': cfg.trunc_normal_sb_msgs = 1; break;
			case 'p': cfg.shorten_title_paths = 1; break;
		}
		++p;
	}

	ui_views_update_titles();
}

/* Handles changes of 'showtabline' option that controls visibility of tab
 * line. */
static void
showtabline_handler(OPT_OP op, optval_t val)
{
	cfg.show_tab_line = (val.enum_item > 2 ? val.enum_item - 3 : val.enum_item);
	stats_redraw_later();
}

/* Handles changes of 'sizefmt' option by parsing string value and updating
 * configuration values. */
static void
sizefmt_handler(OPT_OP op, optval_t val)
{
	char *const new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	int base = -1, precision = 0, space = 1;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(starts_with_lit(part, "units:"))
		{
			const char *const val = after_first(part, ':');
			if(strcmp(val, "iec") == 0)
			{
				base = 1024;
			}
			else if(strcmp(val, "si") == 0)
			{
				base = 1000;
			}
			else
			{
				vle_tb_append_linef(vle_err, "Invalid units value: %s", val);
				break;
			}
		}
		else if(starts_with_lit(part, "precision:"))
		{
			const char *const val = after_first(part, ':');
			if(!read_int(val, &precision) || precision <= 0)
			{
				vle_tb_append_linef(vle_err, "Invalid precision value: %s", val);
				break;
			}
		}
		else if(strcmp(part, "space") == 0)
		{
			space = 1;
		}
		else if(strcmp(part, "nospace") == 0)
		{
			space = 0;
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'sizefmt' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(part == NULL && base != -1)
	{
		cfg.sizefmt.base = base;
		cfg.sizefmt.precision = precision;
		cfg.sizefmt.space = space;

		stats_redraw_later();
	}
	else if(base == -1)
	{
		vle_tb_append_line(vle_err, "'sizefmt' value is missing units type");
	}

	/* In case of error, restore previous value, otherwise reload it anyway to
	 * remove any duplicates. */
	vle_opts_assign("sizefmt", make_sizefmt_value(), OPT_GLOBAL);
}

/* Makes string value describing 'sizefmt' from current configuration state.
 * The value is statically allocated.  Returns the value. */
static optval_t
make_sizefmt_value(void)
{
	static char value[128];
	static const optval_t val = { .str_val = value };

	int len = snprintf(value, sizeof(value), "units:%s",
			cfg.sizefmt.base == 1024 ? "iec" : "si");
	if(cfg.sizefmt.precision != 0)
	{
		len += snprintf(value + len, sizeof(value) - len, ",precision:%d",
				cfg.sizefmt.precision);
	}
	if(cfg.sizefmt.space == 0)
	{
		len += snprintf(value + len, sizeof(value) - len, ",nospace");
	}

	return val;
}

#ifndef _WIN32
static void
slowfs_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.slow_fs_list, val.str_val);
}
#endif

static void
smartcase_handler(OPT_OP op, optval_t val)
{
	cfg.smart_case = val.bool_val;
}

static void
sortnumbers_handler(OPT_OP op, optval_t val)
{
	cfg.sort_numbers = val.bool_val;
	resort_dir_list(1, curr_view);
	resort_dir_list(1, other_view);
	redraw_lists();
}

/* Handles switch that controls visibility of dot files globally. */
static void
dotfiles_global(OPT_OP op, optval_t val)
{
	curr_view->hide_dot_g = !val.bool_val;
}

/* Handles switch that controls visibility of dot files locally. */
static void
dotfiles_local(OPT_OP op, optval_t val)
{
	curr_view->hide_dot = !val.bool_val;
	ui_view_schedule_reload(curr_view);
}

/* Handles update of ls settings as a global option. */
static void
lsoptions_global(OPT_OP op, optval_t val)
{
	set_lsoptions(&curr_view->ls_transposed_g, &curr_view->ls_cols_g, val,
			OPT_GLOBAL);
}

/* Handles update of ls settings as a local option. */
static void
lsoptions_local(OPT_OP op, optval_t val)
{
	set_lsoptions(&curr_view->ls_transposed, &curr_view->ls_cols, val, OPT_LOCAL);
}

/* Handles update of ls-view settings. */
static void
set_lsoptions(int *transposed, int *column_count, optval_t val, OPT_SCOPE scope)
{
	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	int transpose = 0;
	int fixed_columns = 0;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		const char *option = part;
		if(strcmp(option, "transposed") == 0)
		{
			transpose = 1;
		}
		else if(skip_prefix(&option, "columncount:"))
		{
			if(!read_int(option, &fixed_columns))
			{
				vle_tb_append_linef(vle_err,
						"Failed to parse \"columncount\" value: %s", option);
				break;
			}
			if(fixed_columns < 0)
			{
				vle_tb_append_linef(vle_err,
						"\"columncount\" can't be less than 0, got: %s", option);
				break;
			}
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'lsoptions' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		*transposed = transpose;
		*column_count = fixed_columns;

		if(!ui_view_displays_columns(curr_view) &&
				transposed == &curr_view->ls_transposed)
		{
			ui_view_schedule_redraw(curr_view);
		}
	}

	fill_lsoptions(&val, *transposed, *column_count);
	vle_opts_assign("lsoptions", val, scope);
}

/* Loads value of lsoptions as a string. */
static void
fill_lsoptions(optval_t *val, int transposed, int column_count)
{
	static char buf[64];
	snprintf(buf, sizeof(buf), "columncount:%d%s", column_count,
			transposed ? ",transposed" : "");
	val->str_val = buf;
}

/* Handles switch that controls column vs. ls-like view in global option. */
static void
lsview_global(OPT_OP op, optval_t val)
{
	curr_view->ls_view_g = val.bool_val;
}

/* Handles switch that controls column vs. ls-like view in local option. */
static void
lsview_local(OPT_OP op, optval_t val)
{
	fview_set_lsview(curr_view, val.bool_val);
}

/* Handles update of miller columns settings as a global option. */
static void
milleroptions_global(OPT_OP op, optval_t val)
{
	set_milleroptions(curr_view->miller_ratios_g, &curr_view->miller_preview_g,
			val, OPT_GLOBAL);
}

/* Handles update of miller columns settings as a local option. */
static void
milleroptions_local(OPT_OP op, optval_t val)
{
	set_milleroptions(curr_view->miller_ratios, &curr_view->miller_preview, val,
			OPT_LOCAL);
}

/* Handles update of miller columns settings. */
static void
set_milleroptions(int ratios[3], MillerPreview *preview, optval_t val,
		OPT_SCOPE scope)
{
	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	int lsize = 0, csize = 1, rsize = 0;
	MillerPreview preview_what = MP_DIRS;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(starts_with_lit(part, "lsize:"))
		{
			const char *const num = after_first(part, ':');
			if(!read_int(num, &lsize))
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"lsize\" value: %s",
						num);
				break;
			}
		}
		else if(starts_with_lit(part, "csize:"))
		{
			const char *const num = after_first(part, ':');
			if(!read_int(num, &csize))
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"csize\" value: %s",
						num);
				break;
			}
			if(csize < 1)
			{
				vle_tb_append_linef(vle_err, "\"csize\" can't be less than 1, got: %s",
						num);
				break;
			}
		}
		else if(starts_with_lit(part, "rsize:"))
		{
			const char *const num = after_first(part, ':');
			if(!read_int(num, &rsize))
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"rsize\" value: %s",
						num);
				break;
			}
		}
		else if(starts_with_lit(part, "rpreview:"))
		{
			const char *const str = after_first(part, ':');
			if(strcmp(str, "all") == 0)
			{
				preview_what = MP_ALL;
			}
			else if(strcmp(str, "dirs") == 0)
			{
				preview_what = MP_DIRS;
			}
			else if(strcmp(str, "files") == 0)
			{
				preview_what = MP_FILES;
			}
			else
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"rpreview\" value: %s",
						str);
				break;
			}
		}
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err, "Unknown key for 'milleroptions' option: %s",
					part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		ratios[0] = MAX(0, MIN(100, lsize));
		ratios[1] = MAX(0, MIN(100, csize));
		ratios[2] = MAX(0, MIN(100, rsize));
		*preview = preview_what;

		if(ratios == curr_view->miller_ratios)
		{
			ui_view_schedule_redraw(curr_view);
		}
	}

	fill_milleroptions(&val, ratios, *preview);
	vle_opts_assign("milleroptions", val, scope);
}

/* Loads value of milleroptions as a string. */
static void
fill_milleroptions(optval_t *val, int ratios[3], MillerPreview preview)
{
	static char buf[64];

	const char *rpreview = (preview == MP_DIRS) ? "dirs"
	                     : (preview == MP_FILES) ? "files" : "all";

	snprintf(buf, sizeof(buf), "lsize:%d,csize:%d,rsize:%d,rpreview:%s",
			ratios[0], ratios[1], ratios[2], rpreview);
	val->str_val = buf;
}

/* Handles switch that controls cascading lists view in global option. */
static void
millerview_global(OPT_OP op, optval_t val)
{
	curr_view->miller_view_g = val.bool_val;
}

/* Handles switch that controls cascading lists view in local option. */
static void
millerview_local(OPT_OP op, optval_t val)
{
	fview_set_millerview(curr_view, val.bool_val);
}

/* Handles file numbers displaying toggle in global option. */
static void
number_global(OPT_OP op, optval_t val)
{
	update_num_type(curr_view, &curr_view->num_type_g, NT_SEQ, val.bool_val);
}

/* Handles file numbers displaying toggle in local option. */
static void
number_local(OPT_OP op, optval_t val)
{
	update_num_type(curr_view, &curr_view->num_type, NT_SEQ, val.bool_val);
}

/* Handles changes of minimum width of file number field in global option. */
static void
numberwidth_global(OPT_OP op, optval_t val)
{
	set_numberwidth(curr_view, &curr_view->num_width_g, val.int_val);
}

/* Handles changes of minimum width of file number field in local option. */
static void
numberwidth_local(OPT_OP op, optval_t val)
{
	set_numberwidth(curr_view, &curr_view->num_width, val.int_val);
}

/* Sets number width for the view. */
static void
set_numberwidth(view_t *view, int *num_width, int width)
{
	*num_width = width;

	if(ui_view_displays_numbers(view))
	{
		redraw_view(view);
	}
}

/* Handles change of global value of the 'previewprg' option. */
static void
previewprg_global(OPT_OP op, optval_t val)
{
	replace_string(&curr_view->preview_prg_g, val.str_val);
}

/* Handles change of local value of the 'previewprg' option. */
static void
previewprg_local(OPT_OP op, optval_t val)
{
	replace_string(&curr_view->preview_prg, val.str_val);
	stats_redraw_later();
}

/* Handles relative file numbers displaying toggle in global option. */
static void
relativenumber_global(OPT_OP op, optval_t val)
{
	update_num_type(curr_view, &curr_view->num_type_g, NT_REL, val.bool_val);
}

/* Handles relative file numbers displaying toggle in local option. */
static void
relativenumber_local(OPT_OP op, optval_t val)
{
	update_num_type(curr_view, &curr_view->num_type, NT_REL, val.bool_val);
}

/* Handles toggling of boolean number related option and updates current view if
 * needed. */
static void
update_num_type(view_t *view, NumberingType *num_type, NumberingType type,
		int enable)
{
	const NumberingType old_num_type = *num_type;

	*num_type = enable
	          ? (old_num_type | type)
	          : (old_num_type & ~type);

	if(*num_type != old_num_type)
	{
		redraw_view(view);
	}
}

/* Handler for global 'sort' option, parses the value and checks it for
 * correctness. */
static void
sort_global(OPT_OP op, optval_t val)
{
	set_sort(curr_view, curr_view->sort_g, val.str_val);
}

/* Handler for local 'sort' option, parses the value and checks it for
 * correctness. */
static void
sort_local(OPT_OP op, optval_t val)
{
	/* Make sure we don't sort unsorted custom view on :restart or when it's a
	 * compare view. */
	signed char *const sort = (curr_stats.restart_in_progress != RT_NONE ||
	                           cv_compare(curr_view->custom.type))
	                        ? ui_view_sort_list_get(curr_view, curr_view->sort)
	                        : curr_view->sort;
	set_sort(curr_view, sort, val.str_val);
}

/* Sets sorting value for the view. */
static void
set_sort(view_t *view, signed char sort_keys[], char order[])
{
	char *part = order, *state = NULL;
	int key_count = 0;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		char *name = part;
		int reverse = 0;
		int pos;
		int key_pos;

		if(*name == '-' || *name == '+')
		{
			reverse = (*name == '-');
			++name;
		}

		pos = string_array_pos((char **)&sort_enum[1], ARRAY_LEN(sort_enum) - 1,
				name);
		if(pos == -1)
		{
			if(name[0] != '\0')
			{
				vle_tb_append_linef(vle_err, "Skipped unknown 'sort' value: %s", name);
				error = 1;
			}
			continue;
		}
		++pos;

		for(key_pos = 0; key_pos < key_count; ++key_pos)
		{
			if(abs(sort_keys[key_pos]) == pos)
			{
				break;
			}
		}

		sort_keys[key_pos] = reverse ? -pos : pos;

		if(key_pos == key_count)
		{
			++key_count;
		}
	}

	if(key_count < SK_COUNT)
	{
		sort_keys[key_count] = SK_NONE;
	}
	ui_view_sort_list_ensure_well_formed(view, sort_keys);

	if(sort_keys == view->sort)
	{
		sorting_changed(view, 0);
	}

	load_sort_option_inner(view, sort_keys);
}

/* Handles global 'sortgroup' option changes. */
static void
sortgroups_global(OPT_OP op, optval_t val)
{
	set_sortgroups(curr_view, &curr_view->sort_groups_g, val.str_val);
}

/* Handles local 'sortgroup' option changes. */
static void
sortgroups_local(OPT_OP op, optval_t val)
{
	set_sortgroups(curr_view, &curr_view->sort_groups, val.str_val);
	sorting_changed(curr_view, 0);
}

/* Sets sort_groups fields (*opt) of the view to the value handling malformed
 * values correctly. */
static void
set_sortgroups(view_t *view, char **opt, char value[])
{
	OPT_SCOPE scope = (opt == &view->sort_groups) ? OPT_LOCAL : OPT_GLOBAL;
	int failure = 0;

	char *first = NULL;
	char *group = value, *state = NULL;
	while((group = split_and_get(group, ',', &state)) != NULL)
	{
		regex_t regex;
		const int err = regexp_compile(&regex, group, REG_EXTENDED | REG_ICASE);
		if(err != 0)
		{
			vle_tb_append_linef(vle_err, "Regexp error in %s: %s", group,
					get_regexp_error(err, &regex));
			failure = 1;
		}
		regfree(&regex);

		if(first == NULL)
		{
			first = strdup(group);
		}
	}

	if(failure)
	{
		optval_t val = { .str_val = *opt };

		free(first);
		error = 1;
		vle_opts_assign("viewcolumns", val, scope);
		return;
	}

	if(first != NULL)
	{
		if(scope == OPT_LOCAL)
		{
			if(view->primary_group_set)
			{
				regfree(&view->primary_group);
			}
			(void)regexp_compile(&view->primary_group, first,
					REG_EXTENDED | REG_ICASE);
			view->primary_group_set = 1;
		}
		free(first);
	}

	replace_string(opt, value);
}

/* Reacts on changes of view sorting. */
static void
sorting_changed(view_t *view, int defer_slow)
{
	/* Reset search results, which might be outdated after resorting. */
	view->matches = 0;
	fview_sorting_updated(view);
	if(!defer_slow)
	{
		resort_view(view);
	}
}

/* Handles global 'sortorder' option and corrects ordering for primary sorting
 * key. */
static void
sortorder_global(OPT_OP op, optval_t val)
{
	set_sortorder(curr_view, (val.enum_item == 1) ? 0 : 1, curr_view->sort_g);
}

/* Handles local 'sortorder' option and corrects ordering for primary sorting
 * key. */
static void
sortorder_local(OPT_OP op, optval_t val)
{
	set_sortorder(curr_view, (val.enum_item == 1) ? 0 : 1, curr_view->sort);
}

/* Updates sorting order for the view. */
static void
set_sortorder(view_t *view, int ascending, signed char sort_keys[])
{
	if((ascending ? +1 : -1)*sort_keys[0] < 0)
	{
		sort_keys[0] = -sort_keys[0];

		if(sort_keys == view->sort)
		{
			resort_view(view);
			load_sort_option(view);
		}
	}
}

/* Handler of global 'viewcolumns' option, which defines custom view columns. */
static void
viewcolumns_global(OPT_OP op, optval_t val)
{
	const char *new_value =
		val.str_val[0] == '\0' ? DEFAULT_VIEW_COLUMNS : val.str_val;

	if(parse_columns(/*columns=*/NULL, &add_column, &map_name, new_value,
				curr_view) != 0)
	{
		vle_tb_append_line(vle_err, "Invalid format of 'viewcolumns' option");
		error = 1;

		val.str_val = curr_view->view_columns_g;
		vle_opts_assign("viewcolumns", val, OPT_GLOBAL);
	}
	else
	{
		replace_string(&curr_view->view_columns_g, new_value);
	}
}

/* Handler of local 'viewcolumns' option, which defines custom view columns. */
static void
viewcolumns_local(OPT_OP op, optval_t val)
{
	set_viewcolumns(curr_view, val.str_val);
}

/* Setups view columns for the view. */
static void
set_viewcolumns(view_t *view, const char view_columns[])
{
	const int update_columns_ui = ui_view_displays_columns(view);
	set_view_columns_option(view, view_columns, update_columns_ui);
}

void
load_dot_filter_option(const view_t *view)
{
	const optval_t val = { .bool_val = !view->hide_dot };
	vle_opts_assign("dotfiles", val, OPT_GLOBAL);
	vle_opts_assign("dotfiles", val, OPT_LOCAL);
}

void
load_view_columns_option(view_t *view, const char value[])
{
	set_view_columns_option(view, value, 1);
}

/* Updates view columns value as if 'viewcolumns' option has been changed.
 * Doesn't change actual value of the option, which is important for setting
 * sorting order via sort dialog. */
static void
set_view_columns_option(view_t *view, const char value[], int update_ui)
{
	const char *new_value = (value[0] == '\0') ? DEFAULT_VIEW_COLUMNS : value;
	columns_t *columns = update_ui ? view->columns : NULL;

	if(update_ui)
	{
		columns_clear(columns);
	}

	if(parse_columns(columns, &add_column, &map_name, new_value, view) != 0)
	{
		optval_t val;

		vle_tb_append_line(vle_err, "Invalid format of 'viewcolumns' option");
		error = 1;

		if(update_ui)
		{
			const char *old_value = (view->view_columns[0] == '\0')
			                      ? DEFAULT_VIEW_COLUMNS
			                      : view->view_columns;
			(void)parse_columns(columns, &add_column, &map_name, old_value, view);
		}

		val.str_val = view->view_columns;
		vle_opts_assign("viewcolumns", val, OPT_LOCAL);
	}
	else
	{
		/* Set value specified by user.  Can't use DEFAULT_VIEW_COLUMNS here,
		 * as we need empty value of view->view_columns to signal about disabled
		 * columns customization. */
		(void)replace_string(&view->view_columns, value);
		if(update_ui)
		{
			ui_view_schedule_redraw(view);
		}
	}
}

/* Adds new column to view columns. */
static void
add_column(columns_t *columns, column_info_t column_info)
{
	/* Handle dry run mode, when we don't actually update column view. */
	if(columns != NULL)
	{
		columns_add_column(columns, column_info);
	}
}

/* Maps column name to column id.  Returns column id or -1 on error. */
static int
map_name(const char name[], void *arg)
{
	/* Handle secondary key (designated by {}). */
	if(*name == '\0')
	{
		const view_t *const view = arg;
		const signed char *const sort = ui_view_sort_list_get(view, view->sort);
		return (int)get_secondary_key((SortingKey)abs(sort[0]));
	}

	int id = ui_map_column_name(name);
	if(id == -1)
	{
		vle_tb_append_linef(vle_err, "Failed to find column: %s", name);
	}
	return id;
}

void
load_quickview_option(void)
{
	optval_t val;
	val.bool_val = curr_stats.preview.on;
	vle_opts_assign("quickview", val, OPT_GLOBAL);
}

void
load_tabscope_option(void)
{
	optval_t val = { .enum_item = (cfg.pane_tabs ? 1 : 0) };
	vle_opts_assign("tabscope", val, OPT_GLOBAL);
}

void
load_history_option(void)
{
	optval_t val = { .int_val = cfg.history_len };
	vle_opts_assign("history", val, OPT_GLOBAL);
}

void
load_geometry(void)
{
	optval_t val;
	val.int_val = cfg.columns;
	vle_opts_assign("columns", val, OPT_GLOBAL);
	val.int_val = cfg.lines;
	vle_opts_assign("lines", val, OPT_GLOBAL);
}

static void
resort_view(view_t * view)
{
	resort_dir_list(1, view);
	ui_view_schedule_redraw(curr_view);
}

/* Configures what is stored as part of a session file. */
static void
sessionoptions_handler(OPT_OP op, optval_t val)
{
	cfg.session_options = val.set_items;
}

static void
statusline_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.status_line, val.str_val);
	stats_redraw_later();
}

/* Sets when to display key suggestions. */
static void
suggestoptions_handler(OPT_OP op, optval_t val)
{
	int new_value = 0;
	int maxregfiles = 5;
	int delay = 500;

	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;

	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		if(strcmp(part, "normal") == 0)           new_value |= SF_NORMAL;
		else if(strcmp(part, "visual") == 0)      new_value |= SF_VISUAL;
		else if(strcmp(part, "view") == 0)        new_value |= SF_VIEW;
		else if(strcmp(part, "otherpane") == 0)   new_value |= SF_OTHERPANE;
		else if(strcmp(part, "keys") == 0)        new_value |= SF_KEYS;
		else if(strcmp(part, "marks") == 0)       new_value |= SF_MARKS;
		else if(strcmp(part, "foldsubkeys") == 0) new_value |= SF_FOLDSUBKEYS;
		else if(starts_with_lit(part, "delay:"))
		{
			const char *const num = after_first(part, ':');
			new_value |= SF_DELAY;
			if(!read_int(after_first(part, ':'), &delay))
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"delay\" value: %s",
						num);
				break;
			}
			if(delay < 0)
			{
				vle_tb_append_line(vle_err, "Delay can't be negative");
				break;
			}
		}
		else if(starts_with_lit(part, "registers:"))
		{
			const char *const num = after_first(part, ':');
			new_value |= SF_REGISTERS;
			if(!read_int(after_first(part, ':'), &maxregfiles))
			{
				vle_tb_append_linef(vle_err, "Failed to parse \"registers\" value: %s",
						num);
				break;
			}
			if(maxregfiles <= 0)
			{
				vle_tb_append_line(vle_err,
						"Must be at least one displayed register file");
				break;
			}
		}
		else if(strcmp(part, "delay") == 0)     new_value |= SF_DELAY;
		else if(strcmp(part, "registers") == 0) new_value |= SF_REGISTERS;
		else
		{
			break_at(part, ':');
			vle_tb_append_linef(vle_err,
					"Invalid key for 'suggestoptions' option: %s", part);
			break;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		cfg.sug.flags = new_value;
		cfg.sug.maxregfiles = maxregfiles;
		cfg.sug.delay = delay;
	}
	else
	{
		reset_suggestoptions();
	}
}

/* Sets value of 'suggestoptions' based on current configuration values. */
static void
reset_suggestoptions(void)
{
	optval_t val;
	vle_textbuf *const descr = vle_tb_create();

	if(cfg.sug.flags & SF_NORMAL)    vle_tb_appendf(descr, "%s", "normal,");
	if(cfg.sug.flags & SF_VISUAL)    vle_tb_appendf(descr, "%s", "visual,");
	if(cfg.sug.flags & SF_VIEW)      vle_tb_appendf(descr, "%s", "view,");
	if(cfg.sug.flags & SF_OTHERPANE) vle_tb_appendf(descr, "%s", "otherpane,");
	if(cfg.sug.flags & SF_KEYS)      vle_tb_appendf(descr, "%s", "keys,");
	if(cfg.sug.flags & SF_MARKS)     vle_tb_appendf(descr, "%s", "marks,");
	if(cfg.sug.flags & SF_FOLDSUBKEYS)
	{
		vle_tb_appendf(descr, "%s", "foldsubkeys,");
	}
	if(cfg.sug.flags & SF_DELAY)
	{
		if(cfg.sug.delay == 500)
		{
			vle_tb_appendf(descr, "%s", "delay,");
		}
		else
		{
			vle_tb_appendf(descr, "delay:%d,", cfg.sug.delay);
		}
	}
	if(cfg.sug.flags & SF_REGISTERS)
	{
		if(cfg.sug.maxregfiles == 5)
		{
			vle_tb_appendf(descr, "%s", "registers,");
		}
		else
		{
			vle_tb_appendf(descr, "registers:%d,", cfg.sug.maxregfiles);
		}
	}

	val.str_val = (char *)vle_tb_get_data(descr);
	vle_opts_assign("suggestoptions", val, OPT_GLOBAL);

	vle_tb_free(descr);
}

/* Handles changes of identifier of group of instances that synchronize their
 * registers. */
static void
syncregs_handler(OPT_OP op, optval_t val)
{
	if(val.str_val[0] == '\0')
	{
		/* Option disabled -> disable functionality. */
		regs_sync_disable();
	}
	else
	{
		/* Option enabled -> enable functionality. */
		regs_sync_enable(val.str_val);
	}
}

/* Makes vifm prefer to perform file-system operations with external
 * applications on rather then with system calls.  The option will be eventually
 * removed.  Mostly *nix-like systems are affected. */
static void
syscalls_handler(OPT_OP op, optval_t val)
{
	cfg.use_system_calls = val.bool_val;
}

/* Sets format string for main part of tab label. */
static void
tablabel_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.tab_label, val.str_val);
	stats_redraw_later();
}

/* Sets format string for the whole tab line. */
static void
tabline_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.tab_line, val.str_val);
	stats_redraw_later();
}

/* Sets format string for tab label's prefix. */
static void
tabprefix_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.tab_prefix, val.str_val);
	stats_redraw_later();
}

/* Sets scope of a single tab. */
static void
tabscope_handler(OPT_OP op, optval_t val)
{
	/* Full restart doesn't try to preserve tabs. */
	if(curr_stats.restart_in_progress == RT_MOST)
	{
		return;
	}

	/* Erase tabs on current scope before switching it. */
	tabs_only(curr_view);
	tabs_only(other_view);
	tabs_rename(curr_view, NULL);

	cfg.pane_tabs = val.bool_val;
	stats_redraw_later();
}

static void
tabstop_handler(OPT_OP op, optval_t val)
{
	if(val.int_val <= 0)
	{
		vle_tb_append_linef(vle_err, "Argument must be positive: %d", val.int_val);
		error = 1;
		vle_opts_restore_default("tabstop", OPT_GLOBAL);
		return;
	}

	cfg.tab_stop = val.int_val;
	text_option_changed();
}

/* Sets format string for tab label's suffix. */
static void
tabsuffix_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.tab_suffix, val.str_val);
	stats_redraw_later();
}

/* Sets format according to which time is displayed. */
static void
timefmt_handler(OPT_OP op, optval_t val)
{
	replace_string(&cfg.time_format, val.str_val);
	redraw_lists();
}

/* Maximum period on waiting for the input.  Works together with
 * mintimeoutlen. */
static void
timeoutlen_handler(OPT_OP op, optval_t val)
{
	if(val.int_val < 0)
	{
		vle_tb_append_linef(vle_err, "Argument must be >= 0: %d", val.int_val);
		error = 1;
		val.int_val = 0;
		vle_opts_assign("timeoutlen", val, OPT_GLOBAL);
		return;
	}

	cfg.timeout_len = val.int_val;
}

/* Whether to update terminal title according to current location or not. */
static void
title_handler(OPT_OP op, optval_t val)
{
	cfg.set_title = val.bool_val;
	if(cfg.set_title)
	{
		ui_view_title_update(curr_view);
	}
	else
	{
		term_title_update(NULL);
	}
}

static void
trash_handler(OPT_OP op, optval_t val)
{
	cfg.use_trash = val.bool_val;
}

static void
trashdir_handler(OPT_OP op, optval_t val)
{
	if(curr_stats.restart_in_progress != RT_NONE && op == OP_RESET)
	{
		/* This must be a valid value, just set it without checks.  It will be
		 * applied at the end of the restart.  This avoids creation of unwanted
		 * creation of the trash at default location. */
		copy_str(cfg.trash_dir, sizeof(cfg.trash_dir), val.str_val);
		return;
	}

	if(trash_set_specs(val.str_val) != 0)
	{
		/* Reset the 'trashdir' option to its previous value. */
		val.str_val = cfg.trash_dir;
		vle_opts_assign("trashdir", val, OPT_GLOBAL);
	}
}

/* Parses set of TUI flags and changes appearance configuration accordingly. */
static void
tuioptions_handler(OPT_OP op, optval_t val)
{
	const char *p;

	/* Turn all flags off. */
	cfg.extra_padding = 0;
	cfg.side_borders_visible = 0;
	cfg.use_unicode_characters = 0;
	cfg.flexible_splitter = 0;
	cfg.ellipsis_position = 0;

	/* And set the ones present in the value. */
	p = val.str_val;
	while(*p != '\0')
	{
		switch(*p)
		{
			case 'l':
				cfg.ellipsis_position = -1;
				break;
			case 'p':
				cfg.extra_padding = 1;
				break;
			case 'r':
				cfg.ellipsis_position = 1;
				break;
			case 's':
				cfg.side_borders_visible = 1;
				break;
			case 'u':
				cfg.use_unicode_characters = 1;
				break;
			case 'v':
				cfg.flexible_splitter = 1;
				break;

			default:
				assert(0 && "Unhandled tuioptions flag.");
				break;
		}
		++p;
	}

	/* Make sure "r" and "l" flags don't appear at the same time. */
	init_tuioptions(&val);
	vle_opts_assign("tuioptions", val, OPT_GLOBAL);

	curr_stats.ellipsis = (cfg.use_unicode_characters ? "…" : "...");
	columns_set_ellipsis(curr_stats.ellipsis);

	stats_redraw_later();
}

/* Applies new value of 'uioptions' to configuration. */
static void
uioptions_handler(OPT_OP op, optval_t val)
{
	cfg.always_show_io_details = ((val.set_items & 1) != 0);
}

static void
undolevels_handler(OPT_OP op, optval_t val)
{
	cfg.undo_levels = val.int_val;
}

static void
vicmd_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.vi_command, val.str_val);
	cfg.vi_cmd_bg = cut_suffix(cfg.vi_command, "&");
}

static void
vixcmd_handler(OPT_OP op, optval_t val)
{
	(void)replace_string(&cfg.vi_x_command, val.str_val);
	cfg.vi_x_cmd_bg = cut_suffix(cfg.vi_x_command, "&");
}

/* Configures what is stored between sessions. */
static void
vifminfo_handler(OPT_OP op, optval_t val)
{
	cfg.vifm_info = val.set_items;
}

static void
vimhelp_handler(OPT_OP op, optval_t val)
{
	cfg.use_vim_help = val.bool_val;
}

/* Handles changes of 'wildinc'. */
static void
wildinc_handler(OPT_OP op, optval_t val)
{
	char *error;
	matcher_t *m = matcher_alloc_glob(val.str_val, &error);
	if(error != NULL)
	{
		vle_tb_append_linef(vle_err, "Bad pattern: %s", error);
		free(error);

		init_wildinc(&val);
		vle_opts_assign("wildinc", val, OPT_GLOBAL);
		return;
	}

	matcher_free(cfg.wild_inc);
	cfg.wild_inc = m;
}

static void
wildmenu_handler(OPT_OP op, optval_t val)
{
	cfg.wild_menu = val.bool_val;
}

/* Wild menu style, which defines how it's presented. */
static void
wildstyle_handler(OPT_OP op, optval_t val)
{
	/* There are only two possible values. */
	cfg.wild_popup = (val.enum_item == 1);
}

/* Handles setting value of 'wordchars' by parsing list of ranges into character
 * array. */
static void
wordchars_handler(OPT_OP op, optval_t val)
{
	char word_chars[256] = { };

	char *new_val = strdup(val.str_val);
	char *part = new_val, *state = NULL;
	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		int from, to;
		if(parse_range(part, &from, &to) != 0)
		{
			error = 1;
			break;
		}

		while(from <= to)
		{
			word_chars[from++] = 1;
		}
	}
	free(new_val);

	if(part == NULL)
	{
		memcpy(&cfg.word_chars, &word_chars, sizeof(cfg.word_chars));
	}
}

/* Parses range, which can be shortened to single endpoint if first element
 * matches last one.  Returns non-zero on error, otherwise zero is returned. */
static int
parse_range(const char range[], int *from, int *to)
{
	const char *p = range;

	if(parse_endpoint(&p, from) != 0)
	{
		vle_tb_append_linef(vle_err, "Wrong range: %s", range);
		return 1;
	}
	if(*p == '-')
	{
		++p;
		if(parse_endpoint(&p, to) != 0)
		{
			vle_tb_append_linef(vle_err, "Wrong range: %s", range);
			return 1;
		}
	}
	else
	{
		*to = *from;
	}

	if(*p != '\0')
	{
		vle_tb_append_linef(vle_err, "Wrong range: %s", range);
		return 1;
	}

	if(*from > *to)
	{
		vle_tb_append_linef(vle_err, "Inversed range: %s", range);
		return 1;
	}

	return 0;
}

/* Parses single endpoint of a range.  It's either a number or a character.
 * Returns non-zero on error, otherwise zero is returned. */
static int
parse_endpoint(const char **str, int *endpoint)
{
	if(**str == '\0')
	{
		vle_tb_append_line(vle_err, "Range bound can't be empty");
		return 1;
	}

	if(isdigit(**str))
	{
		char *endptr;
		const long int val = strtol(*str, &endptr, 10);
		if(val < 0 || val >= 256)
		{
			vle_tb_append_linef(vle_err, "Wrong value: %ld", val);
			return 1;
		}
		*str = endptr;
		*endpoint = val;
	}
	else
	{
		*endpoint = (unsigned char)**str;
		++*str;
	}
	return 0;
}

static void
wrap_handler(OPT_OP op, optval_t val)
{
	cfg.wrap_quick_view = val.bool_val;
	text_option_changed();
}

/* Updates preview pane and explore mode.  Should be called when any of options
 * that affect those modes is changed. */
static void
text_option_changed(void)
{
	stats_redraw_later();
}

static void
wrapscan_handler(OPT_OP op, optval_t val)
{
	cfg.wrap_scan = val.bool_val;
}

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