/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Variable setting and display
 */

#include "config.h"
#include "keymap.h"
#include "regexp.h"
#include "chset.h"
#include "nn_term.h"

/* variable.c */

static struct variable_defs *lookup_variable __APROTO((char *variable));
static char escaped_char __APROTO((int c));
static void adjust __APROTO((register char *str));
static char *var_value __APROTO((register struct variable_defs *var, char *tag));
static var_on_stack __APROTO((register struct variable_defs *var));

extern int prompt_length, prompt_line;
extern regexp *pg_regexp;
extern int pg_new_regexp;


/*
 * Variable types and variants
 *
 * Boolean: type 'int'.
 *
 *	BOOL 0		Ordinary boolean variable.
 *	BOOL 1		As 0 + redraw screen on return.
 *	BOOL 2		Special: reorder menu according to new value.
 *	BOOL 4		Inverse boolean varible ("set" clears internal var).
 *
 * Numeric: type 'int'.
 *
 *	INT 0		Ordinary numeric variable ("unset" set var to 0).
 *	INT 1		As 0 + redraw screen on return.
 *	INT 2		As 0, but "unset" set var to -1.
 *	INT 3		As 2 + redraw screen on return.
 *
 * Strings: type 'char *'
 *
 *	STR 0		Ordinary string ("unset" set var to NULL).
 *	STR 2		(home relative) file name or full path.
 *			Automatically expanded.  ("unset" set var to NULL).
 *	STR 3		Ordinary string, but cannot be "unset".
 *	STR 4		(Expanded) file name - cannot be unset.
 *	STR 5		Ordinary string ("unset" set var to "").
 *
 *	CODES n		String initialized by list of key names.
 *			A maximum of 16 CODES variables (n = 0 - 15) exist.
 *
 * Strings: type 'char []'
 *
 *	STR 1		Ordinary string saved in static array.
 *			"unset" set array to empty string.
 *			Warning: no bounds checking!
 *			Notice: Variable address is "(char **)var"  (no &).
 *
 * Keys: type 'key_type'
 *
 *	KEY 0		Ordinary key.
 *
 * Pseudo variables:
 *
 *	SPEC n		Treated by V_SPECIAL 'case n'.
 *
 * Modifiers:
 *
 *	INIT		Can only be set in init file.
 *	SAFE		Cannot be changed if "shell-restrictions" is set.
 */

import in_init;

import char			/* string variables */
    *backup_folder_path,
    *bak_suffix,
    *bug_address,
    *counter_delim_left,
    *counter_delim_right,
    *decode_header_file,
    *default_distribution,
    *default_save_file,
    *distribution_follow,
    *distribution_post,
    *editor_program,
    *extra_mail_headers,
    *extra_news_headers,
    *folder_save_file,
    *header_lines,
    *folder_directory,
    included_mark[],
    *inews_program,
    *initial_newsrc_path,
    *mail_alias_expander,
    *mail_box,
    *mail_record,
    *mail_script,
    *mailer_program,
    attributes[],
    *newsrc_file,
    *news_record,
    *news_script,
    *pager,
    patch_command[],
    printer[],
    *print_header_lines,
    *response_dflt_answer,
    *save_counter_format,
    *save_header_lines,
    *saved_header_escape,
    *shade_on_attr,
    *shade_off_attr,
    *spell_checker,
    *trusted_escapes,
    unshar_command[],
    *unshar_header_file,
    *user_shell;

import int			/* boolean variables */
    also_cross_postings,
    also_full_digest,
    also_subgroups,
    append_sig_mail,
    append_sig_post,
    auto_junk_seen,
    auto_select_rw,
    auto_select_subject,
    auto_preview_mode,
    body_search_header,
    case_fold_search,
    check_group_access,
    compress_mode,
    conf_append,
    conf_auto_quit,
    conf_create,
    conf_dont_sleep,
    conf_group_entry,
    conf_junk_seen,
    consolidated_manual,
    consolidated_menu,
    counter_padding,
    decode_keep,
    delay_redraw,
    dflt_kill_select,
    do_kill_handling,
    dont_sort_articles,
    dont_sort_folders,
    dont_split_digests,
    echo_prefix_key,
    edit_patch_command,
    edit_print_command,
    edit_unshar_command,
    empty_answer_check,
    enter_last_read_mode,
    flow_control,
    flush_typeahead,
    fmt_rptsubj,
    folder_format_check,
    folder_rewrite_trace,
    guard_double_slash,
    ignore_formfeed,
    ignore_xon_xoff,
    ignore_re,
    include_art_id,
    include_full_header,
    include_mark_blanks,
    inews_pipe_input,
    keep_backup_folder,
    keep_rc_backup,
    keep_unsubscribed,
    keep_unsub_long,
    long_menu,
    macro_debug,
    mailer_pipe_input,
    mark_overlap,
    mark_overlap_shading,
    match_parts_equal,
    match_parts_begin,
    monitor_mode,
    new_read_prompt,
    novice,
    prev_also_read,
    preview_mark_read,
    query_signature,
    quick_save,
    quick_unread_count,
    read_ret_next_page,
    repeat_group_query,
    report_cost_on_exit,
    retain_seen_status,
    save_report,
    scroll_clear_page,
    select_leave_next,
    select_on_sender,
    seq_cross_filtering,
    shell_restrictions,
    show_article_date,
    show_current_time,
    show_motd_on_entry,
    silent,
    slow_mode,
    suggest_save_file,
    tidy_newsrc,
    use_ed_line,
    use_mail_folders,
    use_mmdf_folders,
    use_path_in_from,
    use_selections,
    use_visible_bell;

import int			/* integer variables */
    also_read_articles,
    article_limit,
    auto_read_limit,
    auto_select_closed,
    check_db_update,
    conf_entry_limit,
    collapse_subject,
    Columns,
    cross_post_limit,
    data_bits,
    Debug,
    decode_skip_prefix,
    entry_message_limit,
    expired_msg_delay,
    first_page_lines,
    fmt_linenum,
    kill_debug,
    kill_ref_count,
    Lines,
    match_skip_prefix,
    mark_next_group,
    mark_read_return,
    mark_read_skip,
    menu_spacing,
    merge_report_rate,
    message_history,
    min_pv_window,
    mouse_usage,
    multi_key_guard_time,
    new_group_action,
    newsrc_update_freq,
    orig_to_include_mask,
    overlap,
    preview_continuation,
    preview_window,
    print_header_type,
    re_layout,
    re_layout_more,
    response_check_pause,
    retry_on_error,
    save_closed_mode,
    save_counter_offset,
    scroll_last_lines,
    show_purpose_mode,
    slow_speed,
    strict_from_parse,
    sort_mode,
    subject_match_limit,
    wrap_headers;

#ifdef NNTP
import char *nn_directory;
import char *nntp_cache_dir, *nntp_server;
import char *nntp_user, *nntp_password;
import int nntp_cache_size, nntp_debug;
#endif

import key_type			/* key strokes */
    comp1_key,
    comp2_key,
    help_key,
    erase_key,
    delword_key,
    kill_key;

#undef STR
#undef BOOL
#undef INT
#undef KEY
#undef SPEC
#undef SAFE
#undef INIT
#undef CODES


#define	V_STRING	0x1000
#define V_BOOLEAN	0x2000
#define	V_INTEGER	0x3000
#define V_KEY		0x4000
#define	V_SPECIAL	0x5000
#define V_CODES		0x6000

#define V_SAFE		0x0100
#define V_INIT		0x0200

#define V_LOCKED	0x0800
#define V_MODIFIED	0x8000

#define	STR		V_STRING |
#define BOOL		V_BOOLEAN |
#define	INT		V_INTEGER |
#define KEY		V_KEY |
#define	SPEC		V_SPECIAL |
#define	CODES		V_CODES |

#define SAFE		V_SAFE |
#define INIT		V_INIT |

static char *code_strings[16] = {
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};

static int var_on_stack();

struct variable_defs {
    char *var_name;
    int  var_flags;
    char **var_addr;
} variables[] = {
    "also-full-digest",		BOOL 0,		(char **)&also_full_digest,
    "also-subgroups",		BOOL INIT 0,	(char **)&also_subgroups,
    "append-signature-mail", 	BOOL 0,		(char **)&append_sig_mail,
    "append-signature-post", 	BOOL 0,		(char **)&append_sig_post,
    "attributes",		STR 1,		(char **)attributes,
    "auto-junk-seen",		BOOL 0,		(char **)&auto_junk_seen,
    "auto-preview-mode",	BOOL 0,		(char **)&auto_preview_mode,
    "auto-read-mode-limit",	INT 0,		(char **)&auto_read_limit,
    "auto-select-closed",	INT 0,		(char **)&auto_select_closed,
    "auto-select-rw",		BOOL 0,		(char **)&auto_select_rw,
    "auto-select-subject",	BOOL 0,		(char **)&auto_select_subject,
    "backup",			BOOL 0,		(char **)&keep_rc_backup,
    "backup-folder-path",	STR 4,		(char **)&backup_folder_path,
    "backup-suffix",		STR 0,		(char **)&bak_suffix,
    "body-search-header",	BOOL 0,		(char **)&body_search_header,
    "bug-report-address", 	STR 0,		(char **)&bug_address,
    "case-fold-search",		BOOL 0,		(char **)&case_fold_search,
    "charset",			SPEC 3,		(char **)NULL,
    "check-db-update-time",	INT 0,		(char **)&check_db_update,
    "check-group-access",	BOOL 0,		(char **)&check_group_access,
    "collapse-subject",		INT 3,		(char **)&collapse_subject,
    "columns",			INT 1,		(char **)&Columns,
    "comp1-key",		KEY 0,		(char **)&comp1_key,
    "comp2-key",		KEY 0,		(char **)&comp2_key,
    "compress",			BOOL 0,		(char **)&compress_mode,
    "confirm-append",		BOOL 0,		(char **)&conf_append,
    "confirm-auto-quit",	BOOL 0,		(char **)&conf_auto_quit,
    "confirm-create",		BOOL 0,		(char **)&conf_create,
    "confirm-entry",		BOOL 0,		(char **)&conf_group_entry,
    "confirm-entry-limit",	INT 0,		(char **)&conf_entry_limit,
    "confirm-junk-seen", 	BOOL 0,		(char **)&conf_junk_seen,
    "confirm-messages",		BOOL 0,		(char **)&conf_dont_sleep,
    "consolidated-manual",	BOOL 0,		(char **)&consolidated_manual,
    "consolidated-menu",	BOOL 1,		(char **)&consolidated_menu,
    "counter-delim-left",	STR 5,		(char **)&counter_delim_left,
    "counter-delim-right",	STR 5,		(char **)&counter_delim_right,
    "counter-padding",		INT 1,		(char **)&counter_padding,
    "cross-filter-seq",		BOOL 0,		(char **)&seq_cross_filtering,
    "cross-post",		BOOL 0,		(char **)&also_cross_postings,
    "cross-post-limit",		INT 0,		(char **)&cross_post_limit,
    "data-bits",		INT 0,		(char **)&data_bits,
    "date",			BOOL 0,		(char **)&show_article_date,
    "debug",			INT 0,		(char **)&Debug,
    "decode-header-file",	STR 0,		(char **)&decode_header_file,
    "decode-keep",		INT 0,		(char **)&decode_keep,
    "decode-skip-prefix",	INT 0,		(char **)&decode_skip_prefix,
    "default-distribution",	STR 0,		(char **)&default_distribution,
    "default-kill-select",	INT 0,		(char **)&dflt_kill_select,
    "default-save-file",	STR 3,		(char **)&default_save_file,
    "delay-redraw",		BOOL 0,		(char **)&delay_redraw,
    "echo-prefix-key",	 	BOOL 0,		(char **)&echo_prefix_key,
    "edit-patch-command", 	BOOL 0,		(char **)&edit_patch_command,
    "edit-print-command", 	BOOL 0,		(char **)&edit_print_command,
    "edit-response-check", 	BOOL 0,		(char **)&empty_answer_check,
    "edit-unshar-command", 	BOOL 0,		(char **)&edit_unshar_command,
    "editor",			STR 0,		(char **)&editor_program,
    "embedded-header-escape",	STR 0,		(char **)&saved_header_escape,
    "enter-last-read-mode",	INT 0,		(char **)&enter_last_read_mode,
    "entry-report-limit", 	INT 0,		(char **)&entry_message_limit,
    "erase-key",		KEY 0,		(char **)&erase_key,
    "expert",			BOOL 4,		(char **)&novice,
    "expired-message-delay",	INT 0,		(char **)&expired_msg_delay,
    "flow-control",		BOOL 0,		(char **)&flow_control,
    "flush-typeahead",		BOOL 0,		(char **)&flush_typeahead,
    "folder",			STR 2,		(char **)&folder_directory,
    "folder-format-check",	BOOL 0,		(char **)&folder_format_check,
    "folder-save-file",		STR 3,		(char **)&folder_save_file,
    "follow-distribution",	STR 0,		(char **)&distribution_follow,
    "from-line-parsing",	INT 0,		(char **)&strict_from_parse,
    "fsort",			BOOL 2,		(char **)&dont_sort_folders,
    "guard-double-slash",	BOOL 0,		(char **)&guard_double_slash,
    "header-lines",		STR 0,		(char **)&header_lines,
    "help-key",			KEY 0,		(char **)&help_key,
    "ignore-formfeed",		BOOL 0,		(char **)&ignore_formfeed,
    "ignore-re",		BOOL 0,		(char **)&ignore_re,
    "ignore-xon-xoff",		BOOL 0,		(char **)&ignore_xon_xoff,
    "include-art-id",		BOOL 0,		(char **)&include_art_id,
    "include-full-header",	BOOL 0,		(char **)&include_full_header,
    "include-mark-blank-lines",	BOOL 0,		(char **)&include_mark_blanks,
    "included-mark",		STR 1,		(char **)included_mark,
    "inews",			STR 0,		(char **)&inews_program,
    "inews-pipe-input",		BOOL 0,		(char **)&inews_pipe_input,
    "initial-newsrc-file",	STR 0,		(char **)&initial_newsrc_path,
    "keep-backup-folder", 	BOOL 0,		(char **)&keep_backup_folder,
    "keep-unsubscribed", 	BOOL 0,		(char **)&keep_unsubscribed,
    "kill",			BOOL 0,		(char **)&do_kill_handling,
    "kill-debug",		BOOL 0,		(char **)&kill_debug,
    "kill-key",			KEY 0,		(char **)&kill_key,
    "kill-reference-count",	INT 0,		(char **)&kill_ref_count,
    "layout",			INT 1,		(char **)&fmt_linenum,
    "limit",			INT 2,		(char **)&article_limit,
    "lines",			INT 1,		(char **)&Lines,
    "long-menu",		BOOL 1,		(char **)&long_menu,
    "macro-debug",		BOOL 0,		(char **)&macro_debug,
    "mail",			STR 2,		(char **)&mail_box,
    "mail-alias-expander",	STR 0,		(char **)&mail_alias_expander,
    "mail-format",		BOOL 0,		(char **)&use_mail_folders,
    "mail-header",		STR 0,		(char **)&extra_mail_headers,
    "mail-record",		STR 2,		(char **)&mail_record,
    "mail-script",		STR SAFE 2,	(char **)&mail_script,
    "mailer",			STR 0,		(char **)&mailer_program,
    "mailer-pipe-input",	BOOL 0,		(char **)&mailer_pipe_input,
    "mark-overlap",		BOOL 0,		(char **)&mark_overlap,
    "mark-overlap-shading",	BOOL 0,		(char **)&mark_overlap_shading,
    "marked-by-next-group",	INT 0,		(char **)&mark_next_group,
    "marked-by-read-return",	INT 0,		(char **)&mark_read_return,
    "marked-by-read-skip",	INT 0,		(char **)&mark_read_skip,
    "menu-spacing",		INT 1,		(char **)&menu_spacing,
    "merge-report-rate",	INT 0,		(char **)&merge_report_rate,
    "message-history",		INT 0,		(char **)&message_history,
    "min-window",		INT 1,		(char **)&min_pv_window,
    "mmdf-format",		BOOL 0,		(char **)&use_mmdf_folders,
    "monitor",			BOOL 0,		(char **)&monitor_mode,
    "motd",			BOOL 0,		(char **)&show_motd_on_entry,
    "mouse-usage",		INT 0,		(char **)&mouse_usage,
    "multi-key-guard-time",	INT 0,		(char **)&multi_key_guard_time,
    "new-group-action",		INT 0,		(char **)&new_group_action,
    "new-style-read-prompt",	BOOL 0,		(char **)&new_read_prompt,
    "news-header",		STR 0,		(char **)&extra_news_headers,
    "news-record",		STR 2,		(char **)&news_record,
    "news-script",		STR SAFE 2,	(char **)&news_script,
    "newsrc",			STR 2,		(char **)&newsrc_file,
    "nn-directory",		STR 2,		(char **)&nn_directory,
#ifdef NNTP
    "nntp-cache-dir",		STR INIT 0,	(char **)&nntp_cache_dir,
    "nntp-cache-size",		INT INIT 0,	(char **)&nntp_cache_size,
    "nntp-debug",		BOOL 0,		(char **)&nntp_debug,
    "nntp-password",		STR 3,		(char **)&nntp_password,
    "nntp-server",		STR 3,		(char **)&nntp_server,
    "nntp-user",		STR 3,		(char **)&nntp_user,
#endif
/*  "no....."  -- cannot have variable names starting with "no" */
    "old",			SPEC 2,		(char **)NULL,
    "orig-to-include-mask",	INT 0,		(char **)&orig_to_include_mask,
    "overlap",			INT 0,		(char **)&overlap,
    "pager",			STR SAFE 3,	(char **)&pager,
    "patch-command",		STR SAFE 1,	(char **)patch_command,
    "post-distribution",	STR 0,		(char **)&distribution_post,
    "preview-continuation", 	INT 0,		(char **)&preview_continuation,
    "preview-mark-read",	BOOL 0,		(char **)&preview_mark_read,
    "previous-also-read",	BOOL 0,		(char **)&prev_also_read,
    "print-header-lines",	STR 0,		(char **)&print_header_lines,
    "print-header-type",	INT 0,		(char **)&print_header_type,
    "printer",			STR SAFE 1,	(char **)printer,
    "query-signature",		BOOL 0,		(char **)&query_signature,
    "quick-count",		BOOL 0,		(char **)&quick_unread_count,
    "quick-save",		BOOL 0,		(char **)&quick_save,
    "re-layout",		INT 0,		(char **)&re_layout,
    "re-layout-read",		INT 0,		(char **)&re_layout_more,
    "read-return-next-page",	BOOL 0,		(char **)&read_ret_next_page,
    "record",			SPEC 1,		(char **)NULL,
    "repeat",			BOOL 0,		(char **)&fmt_rptsubj,
    "repeat-group-query",	BOOL 0,		(char **)&repeat_group_query,
    "report-cost",		BOOL 0,		(char **)&report_cost_on_exit,
    "response-check-pause",	INT 0,		(char **)&response_check_pause,
    "response-default-answer",	STR 0,		(char **)&response_dflt_answer,
    "retain-seen-status", 	BOOL 0,		(char **)&retain_seen_status,
    "retry-on-error",		INT 0,		(char **)&retry_on_error,
    "save-closed-mode",		INT 0,		(char **)&save_closed_mode,
    "save-counter",		STR 3,		(char **)&save_counter_format,
    "save-counter-offset",	INT 0,		(char **)&save_counter_offset,
    "save-header-lines",	STR 0,		(char **)&save_header_lines,
    "save-report",		BOOL 0,		(char **)&save_report,
    "scroll-clear-page",	BOOL 0,		(char **)&scroll_clear_page,
    "scroll-last-lines",	INT 0,		(char **)&scroll_last_lines,
    "select-leave-next",	BOOL 0,		(char **)&select_leave_next,
    "select-on-sender",		BOOL 0,		(char **)&select_on_sender,
    "shading-off",		CODES 0,	(char **)&shade_off_attr,
    "shading-on",		CODES 1,	(char **)&shade_on_attr,
    "shell",			STR SAFE 0,	(char **)&user_shell,
    "shell-restrictions", 	BOOL INIT 0,	(char **)&shell_restrictions,
    "show-purpose-mode",	INT 0,		(char **)&show_purpose_mode,
    "silent",			BOOL 0,		(char **)&silent,
    "slow-mode",		BOOL 0,		(char **)&slow_mode,
    "slow-speed",		INT 0,		(char **)&slow_speed,
    "sort",			BOOL 2,		(char **)&dont_sort_articles,
    "sort-mode",		INT 0,		(char **)&sort_mode,
    "spell-checker",		STR 0,		(char **)&spell_checker,
    "split",			BOOL 4,		(char **)&dont_split_digests,
    "stop",			INT 0,		(char **)&first_page_lines,
    "subject-match-limit", 	INT 0,		(char **)&subject_match_limit,
    "subject-match-minimum",	INT 0,		(char **)&match_parts_begin,
    "subject-match-offset",	INT 0,		(char **)&match_skip_prefix,
    "subject-match-parts",	BOOL 0,		(char **)&match_parts_equal,
    "suggest-default-save", 	BOOL 0,		(char **)&suggest_save_file,
    "tidy-newsrc",		BOOL 0,		(char **)&tidy_newsrc,
    "time",			BOOL 0,		(char **)&show_current_time,
    "trace-folder-packing",	BOOL 0,		(char **)&folder_rewrite_trace,
    "trusted-escape-codes",	STR 0,		(char **)&trusted_escapes,
    "unshar-command",		STR SAFE 1,	(char **)unshar_command,
    "unshar-header-file",	STR 0,		(char **)&unshar_header_file,
    "unsubscribe-mark-read",	BOOL 4,		(char **)&keep_unsub_long,
    "update-frequency",		INT 0,		(char **)&newsrc_update_freq,
    "use-editor-line",		BOOL 0,		(char **)&use_ed_line,
    "use-path-in-from",		BOOL 0,		(char **)&use_path_in_from,
    "use-selections", 		BOOL 0,		(char **)&use_selections,
    "visible-bell", 		BOOL 0,		(char **)&use_visible_bell,
    "window",			INT 1,		(char **)&preview_window,
    "word-key",			KEY 0,		(char **)&delword_key,
    "wrap-header-margin",	INT 2,		(char **)&wrap_headers
};

#define TABLE_SIZE	(sizeof(variables)/sizeof(struct variable_defs))

#define INT_VAR		(*((int *)(var->var_addr)))
#define BOOL_VAR	(*((int *)(var->var_addr)))
#define STR_VAR		(*(var->var_addr))
#define CBUF_VAR	((char *)(var->var_addr))
#define KEY_VAR		(*((key_type *)(var->var_addr)))

#define VAR_TYPE	(var->var_flags & 0x7000)
#define VAR_OP		(var->var_flags & 0x000f)

static struct variable_defs *lookup_variable(variable)
char *variable;
{
    register struct variable_defs *var;
    register i, j, k, t;

    i = 0; j = TABLE_SIZE - 1;

    while (i <= j) {
	k = (i + j) / 2;
	var = &variables[k];

	if ( (t=strcmp(variable, var->var_name)) > 0)
	    i = k+1;
	else
	if (t < 0)
	    j = k-1;
	else
	    return var;
    }

    init_message("unknown variable: %s", variable);
    return NULL;
}


static char escaped_char(c)
char c;
{
    switch (c) {
     case 'a':
	return 007;
     case 'b':
	return BS;
     case 'e':
	return 033;
     case 'f':
	return '\f';
     case 'n':
	return NL;
     case 'r':
	return CR;
     case 't':
	return TAB;
    }
    return c;
}

static void
adjust(str)
register char *str;
{
    register char *s, *t;

    if ((s = t = str) == NULL) return;
    while (*s && *s != '#') {
	if (*s == '\\' && s[1] != NUL) {
	    s++;
	    *str++ = escaped_char(*s++);
	} else
	if (str == s) {
	    str++;
	    if (isspace(*s++)) continue;
	} else
	    if (isspace(*str++ = *s++)) continue;
	t = str;
    }
    *t = NUL;
}

int
set_variable(variable, on, val_string)
char *variable;
int on;
char *val_string;
{
    int value;
    register struct variable_defs *var;

    if (strncmp(variable, "no", 2) == 0) {
	on = !on;
	variable += 2;
	if (variable[0] == '-') variable++;
    }

    if ((var = lookup_variable(variable)) == NULL)
	return 0;

    if (!in_init && (var->var_flags & (V_INIT | V_SAFE))) {
	if (var->var_flags & V_INIT) {
	    msg("'%s' can only be set in the init file", variable);
	    return 0;
	}
	if (shell_restrictions) {
	    msg("Restricted operation - cannot change");
	    return 0;
	}
    }

    if (var->var_flags & V_LOCKED) {
	msg("Variable '%s' is locked", variable);
	return 0;
    }

    if (!on || val_string == NULL)
	value = 0;
    else
	value = atoi(val_string);

    var->var_flags |= V_MODIFIED;

    switch (VAR_TYPE) {

     case V_STRING:

	if (on) adjust(val_string);

	switch (VAR_OP) {
	 case 0:
	    STR_VAR = (on && val_string) ? copy_str(val_string) : (char *)NULL;
	    break;

	 case 1:
	    strcpy(CBUF_VAR, (on && val_string) ? val_string : "");
	    break;

	 case 2:
	    if (on) {
		char exp_buf[FILENAME];

		if (val_string) {
		    if (expand_file_name(exp_buf, val_string, 1))
			STR_VAR = home_relative(exp_buf);
		}
	    } else
		STR_VAR = (char *)NULL;
	    break;

	 case 3:
	 case 4:
	    if (!on || val_string == NULL) {
		msg("Cannot unset string `%s'", variable);
		break;
	    }
	    if (VAR_OP == 4) {
		char exp_buf[FILENAME];
		if (expand_file_name(exp_buf, val_string, 1)) {
		    STR_VAR = copy_str(exp_buf);
		    break;
		}
	    }
	    STR_VAR = copy_str(val_string);
	    break;
	 case 5:
	    STR_VAR = (on && val_string) ? copy_str(val_string) : "";
	    break;

	}
	break;

     case V_BOOLEAN:

	adjust(val_string);
	if (val_string && *val_string != NUL)
	    if (val_string[0] == 'o')
		on = val_string[1] == 'n'; /* on */
	    else
		on = val_string[0] == 't'; /* true */

	switch (VAR_OP) {
	 case 0:
	    BOOL_VAR = on;
	    break;

	 case 1:
	    BOOL_VAR = on;
	    return 1;

	 case 2:
	    if (BOOL_VAR) {	/* don't change if already ok */
		if (!on) break;
	    } else
		if (on) break;

	    BOOL_VAR = !on;
	    if (!in_init) {
		sort_articles(BOOL_VAR ? 0 : -1);
		return 1;
	    }
	    break;

	 case 4:
	    BOOL_VAR = !on;
	    break;
	}
	break;

     case V_INTEGER:

	switch (VAR_OP) {
	 case 0:
	 case 1:
	    INT_VAR = value;
	    break;

	 case 2:
	 case 3:
	    if (!on) value = -1;
	    INT_VAR = value;
	    break;
	}
	return (VAR_OP & 1);

     case V_KEY:
	switch (VAR_OP) {
	 case 0:
	    if (val_string) {
		if (*val_string) adjust(val_string + 1); /* #N is valid */
		KEY_VAR = parse_key(val_string);
	    }
	    break;
	}
	break;

     case V_SPECIAL:

	switch (VAR_OP) {
	 case 1:
	    if (val_string) {
		adjust(val_string);
		news_record = home_relative(val_string);
		mail_record = news_record;
		var->var_flags &= ~V_MODIFIED;
		lookup_variable("mail-record")->var_flags |= V_MODIFIED;
		lookup_variable("news-record")->var_flags |= V_MODIFIED;
	    }
	    break;

	 case 2:
	    also_read_articles = on;
	    article_limit = (on && value > 0) ? value : -1;
	    break;

	 case 3:
	    {
		struct chset *csp;
    		struct variable_defs *dbvar;

		dbvar = lookup_variable("data-bits");

	        if (on && val_string) {
		    if ((csp = getchset(val_string)) == NULL)
		        msg("Illegal value for `%s' variable", variable);
		    else {
		        curchset = csp;
			data_bits = csp->cs_width ? csp->cs_width : 7;
			dbvar->var_flags &= ~V_MODIFIED;
		    }
	        } else
		    msg("Cannot unset special `%s' variable", variable);
	    }
	    break;
	}
	break;

     case V_CODES:
	{
	    char codes[80], code[16], *sp, *cp, *vs;

	    if (val_string == NULL) on = 0;
	    if (on) {
		adjust(val_string);
		if (val_string[0] == NUL) on = 0;
	    }

	    if (on) {
		sp = codes;
		vs = val_string;
		while (*vs) {
		    while (*vs && (!isascii(*vs) || isspace(*vs)))
			vs++;
		    if (*vs == NUL) break;
		    cp = code;
		    while (*vs && isascii(*vs) && !isspace(*vs))
			*cp++ = *vs++;
		    *cp = NUL;
		    *sp++ = parse_key(code);
		}
		*sp = NUL;
		if (codes[0] == NUL) on = 0;
	    }

	    freeobj(code_strings[VAR_OP]);
	    code_strings[VAR_OP] = on ? copy_str(val_string) : NULL;
	    STR_VAR = on ? copy_str(codes) : (char *)NULL;
	    break;
	}
    }
    return 0;
}

void
toggle_variable(variable)
char *variable;
{
    register struct variable_defs *var;

    if ((var = lookup_variable(variable)) == NULL) return;
    if (VAR_TYPE != V_BOOLEAN) {
	init_message("variable %s is not boolean", variable);
	return;
    }

    BOOL_VAR = !BOOL_VAR;
}

void
lock_variable(variable)
char *variable;
{
    register struct variable_defs *var;

    if ((var = lookup_variable(variable)) != NULL)
	var->var_flags |= V_LOCKED;
}


static char *var_value(var, tag)
register struct variable_defs *var;
char *tag;
{
    static char ival[16];
    register char *str = NULL;
    register int b;

    if (tag != NULL)
    *tag = var_on_stack(var) ? '>' :
	(var->var_flags & V_LOCKED) ? '!' :
	(var->var_flags & V_MODIFIED) ? '*' : ' ';

    switch (VAR_TYPE) {
     case V_STRING:
	str = (VAR_OP == 1) ? CBUF_VAR : STR_VAR;
	break;

     case V_BOOLEAN:
	b = BOOL_VAR;
	if (VAR_OP == 2 || VAR_OP == 4) b = !b;
	str = b ? "on" : "off";
	break;

     case V_INTEGER:
	sprintf(ival, "%d", INT_VAR);
	str = ival;
	break;
	
     case V_KEY:
	str = key_name(KEY_VAR);
	break;

     case V_SPECIAL:
	str = "UNDEF";
	switch (VAR_OP) {
	 case 2:
	    if (!also_read_articles) break;
	    sprintf(ival, "%d", article_limit);
	    str = ival;
	    break;
	 case 3:
	    str = curchset->cs_name;
	    break;
	}
	break;

     case V_CODES:
	str = code_strings[VAR_OP];
	break;
    }
    if (str == NULL) str = "NULL";
    return str;
}

int
test_variable(expr)
char *expr;
{
    char *variable;
    register struct variable_defs *var;
    int res = -1;

    variable = expr;
    if ((expr = strchr(variable, '=')) == NULL)
	goto err;

    *expr++ = NUL;

    if ((var = lookup_variable(variable)) == NULL) {
	msg("testing unknown variable %s=%s", variable, expr);
	goto out;
    }

    switch (VAR_TYPE) {

     case V_BOOLEAN:
	res = BOOL_VAR;

	if (strcmp(expr, "on") == 0 || strcmp(expr, "true") == 0) break;
	if (strcmp(expr, "off") == 0 || strcmp(expr, "false") == 0) {
	    res = !res;
	    break;
	}
	msg("boolean variables must be tested =on or =off");
	break;

     case V_INTEGER:
	res = (INT_VAR == atoi(expr)) ? 1 : 0;
	break;

     default:
	msg("%s: cannot only test boolean and integer variables", variable);
	break;
    }
 out:
    *--expr = '=';
 err:
    return res;
}

static int vc_column;

void
var_compl_opts(col)
int col;
{
    vc_column = col;
}

int
var_completion(path, index)
char *path;
int index;
{
    static char *head, *tail = NULL;
    static int len;
    static struct variable_defs *var, *help_var;

    if (index < 0) {
	clrmsg(-(index + 1));
	return 1;
    }

    if (path) {
	head = path;
	tail = path + index;
	while (*head && isspace(*head)) head++;
	if (strncmp(head, "no", 2) == 0) {
	    head += 2;
	    if (*head == '-') head++;
	}

	help_var = var = variables;
	len = tail - head;

	return 1;
    }

    if (index) {
	list_completion((char *)NULL);

	for (;; help_var++) {
	    if (help_var >= &variables[TABLE_SIZE]) {
		help_var = variables;
		break;
	    }

	    index = strncmp(help_var->var_name, head, len);
	    if (index < 0) continue;
	    if (index > 0) {
		help_var = variables;
		break;
	    }
	    if (list_completion(help_var->var_name) == 0) break;
	}
	fl;
	return 1;
    }

    for (; var < &variables[TABLE_SIZE]; var++) {
	if (len == 0)
	    index = 0;
	else
	    index = strncmp(var->var_name, head, len);
	if (index < 0) continue;
	if (index > 0) break;
	sprintf(tail, "%s ", var->var_name + len);
	msg("%.70s", var_value(var, (char *)NULL));
	gotoxy(prompt_length + vc_column, prompt_line);
	var++;
	return 1;
    }
    clrmsg(vc_column);
    return 0;
}

static struct var_stack {
    struct var_stack *next;
    struct variable_defs *v;
    int mod_flag;
    union {
	int ivar;
	int bool;
	char key;
	char *str;
    } value;
} *var_stack = NULL, *vs_pool = NULL;

void
mark_var_stack()
{
    register struct var_stack *vs;

    if (vs_pool) {
	vs = vs_pool;
	vs_pool = vs->next;
    } else
	vs = newobj(struct var_stack, 1);

    vs->next = var_stack;
    var_stack = vs;
    vs->v = NULL;
}

int
push_variable(variable)
char *variable;
{
    register struct variable_defs *var;
    register struct var_stack *vs;

    if (strncmp(variable, "no", 2) == 0) {
	variable += 2;
	if (variable[0] == '-') variable++;
    }

    if ((var = lookup_variable(variable)) == NULL) {
	msg("pushing unknown variable %s", variable);
	return 0;
    }

    mark_var_stack();
    vs = var_stack;
    vs->v = var;
    vs->mod_flag = var->var_flags & V_MODIFIED;

    switch (VAR_TYPE) {

     case V_STRING:

	switch (VAR_OP) {
	 case 0:	/* if we update one of these variables,	*/
	 case 2:	/* new storage will be allocated for it */
	 case 3:	/* so it is ok just to save the pointer */
	 case 4:
	    vs->value.str = STR_VAR;
	    break;

	 case 1:	/* we free this memory when restored	*/
	    vs->value.str = copy_str(CBUF_VAR);
	    break;
	}
	break;

     case V_BOOLEAN:
	vs->value.bool = BOOL_VAR;
	break;

     case V_INTEGER:
	vs->value.ivar = INT_VAR;
	break;

     case V_KEY:
	vs->value.key = KEY_VAR;
	break;

     case V_SPECIAL:
	msg("Cannot push pseudo variable %s", var->var_name);
	break;

     case V_CODES:
	msg("Cannot push code string variable %s", var->var_name);
	break;
    }

    return 1;
}

void
restore_variables()
{
    register struct variable_defs *var;
    register struct var_stack *vs, *vs1;

    vs = var_stack;

    while (vs != NULL) {
	if ((var = vs->v) == NULL) {
	    var_stack = vs->next;
	    vs->next = vs_pool;
	    vs_pool = vs;
	    return;
	}

	var->var_flags &= ~V_MODIFIED;
	var->var_flags |= vs->mod_flag;

	switch (VAR_TYPE) {

	 case V_STRING:
	    switch (VAR_OP) {
	     case 0:	/* only restore the string if changed; then we	*/
	     case 2:	/* can also free the memory occupied by the	*/
	     case 3:	/* 'new' value (if not NULL)			*/
	     case 4:
		if (STR_VAR != vs->value.str) {
		    if (STR_VAR != NULL) freeobj(STR_VAR);
		    STR_VAR = vs->value.str;
		}
		break;

	     case 1:	/* it fitted before, so it will fit againg */
		strcpy(CBUF_VAR, vs->value.str);
		freeobj(vs->value.str);
		break;
	    }
	    break;

	 case V_BOOLEAN:
	    BOOL_VAR = vs->value.bool;
	    break;

	 case V_INTEGER:
	    INT_VAR = vs->value.ivar;
	    break;

	 case V_KEY:
	    KEY_VAR = vs->value.key;
	    break;

	 case V_SPECIAL:	/* these are not saved, so... */
	    break;

	 case V_CODES:
	    break;
	}

	vs1 = vs->next;
	vs->next = vs_pool;
	vs_pool = vs;
	vs = vs1;
    }
    var_stack = NULL;
}

static int
var_on_stack(var)
register struct variable_defs *var;
{
    register struct var_stack *vs;

    for (vs = var_stack; vs; vs = vs->next)
	if (vs->v == var) return 1;
    return 0;
}

void
disp_variables(all, rexp)
int all;
char *rexp;
{
    char *str, pushed;
    register struct variable_defs *var;

    if (in_init) return;

    clrdisp();
    if (novice && !all && rexp == NULL) {
	msg("Use `:set all' to see all variable settings");
	home();
    }

    so_printxy(0, 0, "Variable settings");
    pg_init(1, 1);
    if (rexp) {
	pg_regexp = regcomp(rexp + 1);
	all = 1;
    }

    for (var = variables; var < &variables[TABLE_SIZE]; var++) {
	if (pg_regexp != NULL && regexec(pg_regexp, var->var_name) == 0)
	    continue;
	str = var_value(var, &pushed);
	if (!all && pushed == ' ') continue;
	if (pg_next() < 0) break;
	if (pg_new_regexp) {
	    pg_new_regexp = 0;
	    var = variables;
	    var--;
	    continue;
	}
	if (VAR_TYPE == V_STRING)
	    tprintf("%c %-25.25s = \"%s\"\n", pushed, var->var_name, str);
	else
	    tprintf("%c %-25.25s = %s\n", pushed, var->var_name, str);
    }

    pg_end();
}

void
print_variable_config(f, all)
FILE *f;
int all;
{
    register struct variable_defs *var;
    char *str, tag[2];
    
    tag[1] = NUL;
    for (var = variables; var < &variables[TABLE_SIZE]; var++) {
	if (!all && (var->var_flags & V_MODIFIED) == 0) continue;
	str = var_value(var, tag);
	fprintf(f, "%s%s='%s'\n", all ? tag : "", var->var_name, str);
    }
}

/*
 *	var_options(string_var *, options, result *)
 *
 *	test whether "string_var" contains any of the options
 *	listed in "options" (NUL-separated list of names ended with 
 *	double NUL).  On return, string_var is advanced over all
 *	recognized options and the result will have the bits corresponding
 *	to the recognized options set.
 */

void
var_options(str, options, res)
char **str;
register char *options;
flag_type *res;
{
    char word[128];
    char *optab[32];
    register char **op, *wp, c;
    register int n;
    
    for (op = optab; *options != NUL; op++, options++) {
	*op = options;
	while (*options) options++;
    }
    *op = NULL;
    
    *res = 0;

    options = *str;
    while (*options) {
	for (wp = word; (c = *options) != NUL; *wp++ = c, options++)
	    if (isascii(c) && isspace(c)) break;
	while ((c = *options) && isascii(c) && isspace(c)) options++;
	*wp = NUL;

	for (op = optab, n = 1; *op != NULL; op++, n++) {
	    if (strcmp(word, *op)) continue;
	    *res |= FLAG(n);
	    *str = options;
	    break;
	}
	if (*op == NULL) break;
    }
}
