/* rt-select.c
*/
/* This software is copyrighted as detailed in the LICENSE file. */


#include "EXTERN.h"
#include "common.h"
#include "list.h"
#include "trn.h"
#include "term.h"
#include "final.h"
#include "util.h"
#include "util2.h"
#include "help.h"
#include "hash.h"
#include "cache.h"
#include "bits.h"
#include "artsrch.h"
#include "ng.h"
#include "opt.h"
#include "ngdata.h"
#include "nntpclient.h"
#include "datasrc.h"
#include "addng.h"
#include "nntp.h"
#include "ngstuff.h"
#include "ngsrch.h"
#include "rcstuff.h"
#include "rcln.h"
#include "kfile.h"
#include "intrp.h"
#include "search.h"
#include "rthread.h"
#include "univ.h"
#include "rt-page.h"
#include "rt-util.h"
#include "color.h"
#include "only.h"
#ifdef USE_TK
#include "tkstuff.h"
#include "tktree.h"
#endif
#include "INTERN.h"
#include "rt-select.h"
#include "rt-select.ih"

static char sel_ret;
static char page_char, end_char;
static int disp_status_line;
static bool clean_screen;
static int removed_prompt;
static int force_sel_pos;

#define START_SELECTOR(new_mode)\
    char save_mode = mode;\
    char save_gmode = gmode;\
    bos_on_stop = TRUE;\
    set_mode('s',new_mode)

#define END_SELECTOR()\
    bos_on_stop = FALSE;\
    set_mode(save_gmode,save_mode)

#define PUSH_SELECTOR()\
    int save_sel_mode = sel_mode;\
    bool save_sel_rereading = sel_rereading;\
    bool save_sel_exclusive = sel_exclusive;\
    ART_UNREAD save_selected_count = selected_count;\
    int (*save_extra_commands) _((char_int)) = extra_commands

#define POP_SELECTOR()\
    sel_exclusive = save_sel_exclusive;\
    sel_rereading = save_sel_rereading;\
    selected_count = save_selected_count;\
    extra_commands = save_extra_commands;\
    bos_on_stop = TRUE;\
    if (sel_mode != save_sel_mode) {\
	sel_mode = save_sel_mode;\
	set_selector(0, 0);\
	save_sel_mode = 0;\
    }

#define PUSH_UNIV_SELECTOR()\
    UNIV_ITEM* save_first_univ = first_univ;\
    UNIV_ITEM* save_last_univ = last_univ;\
    UNIV_ITEM* save_page_univ = sel_page_univ;\
    UNIV_ITEM* save_next_univ = sel_next_univ;\
    char* save_univ_fname = univ_fname;\
    char* save_univ_label = univ_label;\
    char* save_univ_title = univ_title;\
    char* save_univ_tmp_file = univ_tmp_file;\
    char save_sel_ret = sel_ret;\
    HASHTABLE* save_univ_ng_hash = univ_ng_hash;\
    HASHTABLE* save_univ_vg_hash = univ_vg_hash

#define POP_UNIV_SELECTOR()\
    first_univ = save_first_univ;\
    last_univ = save_last_univ;\
    sel_page_univ = save_page_univ;\
    sel_next_univ = save_next_univ;\
    univ_fname = save_univ_fname;\
    univ_label = save_univ_label;\
    univ_title = save_univ_title;\
    univ_tmp_file = save_univ_tmp_file;\
    sel_ret = save_sel_ret;\
    univ_ng_hash = save_univ_ng_hash;\
    univ_vg_hash = save_univ_vg_hash

static int (*extra_commands) _((char_int));

/* Display a menu of threads/subjects/articles for the user to choose from.
** If "cmd" is '+' we display all the unread items and allow the user to mark
** them as selected and perform various commands upon them.  If "cmd" is 'U'
** the list consists of previously-read items for the user to mark as unread.
*/
char
article_selector(cmd)
char_int cmd;
{
    bool save_selected_only;
    START_SELECTOR('t');

    sel_rereading = (cmd == 'U');

    art = lastart+1;
    extra_commands = article_commands;
    keep_the_group_static = (keep_the_group_static == 1);

    sel_mode = SM_ARTICLE;
    set_sel_mode(cmd);

    if (!cache_range(sel_rereading? absfirst : firstart, lastart)) {
	sel_ret = '+';
	goto sel_exit;
    }

  sel_restart:
    /* Setup for selecting articles to read or set unread */
    if (sel_rereading) {
	end_char = 'Z';
	page_char = '>';
	sel_page_app = NULL;
	sel_page_sp = NULL;
	sel_mask = AF_DELSEL;
    } else {
	end_char = NewsSelCmds[0];
	page_char = NewsSelCmds[1];
	if (curr_artp) {
	    sel_last_ap = curr_artp;
	    sel_last_sp = curr_artp->subj;
	}
	sel_mask = AF_SEL;
    }
    save_selected_only = selected_only;
    selected_only = TRUE;
    count_subjects(cmd? CS_UNSEL_STORE : CS_NORM);

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;
    *msg = '\0';
    if (added_articles) {
	register long i = added_articles, j;
	for (j = lastart-i+1; j <= lastart; j++) {
	    if (!article_unread(j))
		i--;
	}
	if (i == added_articles)
	    sprintf(msg, "** %ld new article%s arrived **  ",
		(long)added_articles, PLURAL(added_articles));
	else
	    sprintf(msg, "** %ld of %ld new articles unread **  ",
		i, (long)added_articles);
	disp_status_line = 1;
    }
    added_articles = 0;
    if (cmd && selected_count) {
	sprintf(msg+strlen(msg), "%ld article%s selected.",
		(long)selected_count, selected_count == 1? " is" : "s are");
	disp_status_line = 1;
    }
    cmd = 0;

    sel_display();
    if (sel_input() == 'R')
	goto sel_restart;

    sel_cleanup();
    newline();
    if (mousebar_cnt)
	clear_rest();

sel_exit:
    if (sel_ret == '\033')
	sel_ret = '+';
    else if (sel_ret == '`')
	sel_ret = 'Q';
    if (sel_rereading) {
	sel_rereading = FALSE;
	sel_mask = AF_SEL;
    }
    if (sel_mode != SM_ARTICLE || sel_sort == SS_GROUPS
     || sel_sort == SS_STRING) {
	if (artptr_list) {
	    free((char*)artptr_list);
	    artptr_list = sel_page_app = NULL;
	    sort_subjects();
	}
	artptr = NULL;
#ifdef ARTSEARCH
	if (!ThreadedGroup)
	    srchahead = -1;
#endif
    }
#ifdef ARTSEARCH
    else
	srchahead = 0;
#endif
    selected_only = (selected_count != 0);
    if (sel_ret == '+') {
	selected_only = save_selected_only;
	count_subjects(CS_RESELECT);
    }
    else
	count_subjects(CS_UNSELECT);
    if (sel_ret == '+') {
	art = curr_art;
	artp = curr_artp;
    } else
	top_article();
    END_SELECTOR();
    return sel_ret;
}

static void
sel_dogroups()
{
    NGDATA* np;
    int ret;
    int save_selected_count = selected_count;

    for (np = first_ng; np; np = np->next) {
	if (!(np->flags & NF_VISIT))
	    continue;
      do_group:
	if (np->flags & NF_SEL) {
	    np->flags &= ~NF_SEL;
	    save_selected_count--;
	}
	set_ng(np);
	if (np != current_ng) {
	    recent_ng = current_ng;
	    current_ng = np;
	}
	ThreadedGroup = (use_threads && !(np->flags & NF_UNTHREADED));
	printf("Entering %s:", ngname);
#ifdef SCAN_ART
	if (sel_ret == ';') {
	    ret = do_newsgroup(savestr(";"));
	} else
#endif
	    ret = do_newsgroup(nullstr);
	switch (ret) {
	  case NG_NORM:
	  case NG_SELNEXT:
	    set_ng(np->next);
	    break;
	  case NG_NEXT:
	    set_ng(np->next);
	    goto loop_break;
	  case NG_ERROR:
	  case NG_ASK:
	    goto loop_break;
	  case NG_SELPRIOR:
	    while ((np = np->prev) != NULL) {
		if (np->flags & NF_VISIT)
		    goto do_group;
	    }
	    (void) first_page();
	    goto loop_break;
	  case NG_MINUS:
	    np = recent_ng;
#if 0
/* CAA: I'm not sure why I wrote this originally--it seems unnecessary */
	    np->flags |= NF_VISIT;
#endif
	    goto do_group;
#ifdef SUPPORT_NNTP
	  case NG_NOSERVER:
	    nntp_server_died(np->rc->datasrc);
	    (void) first_page();
	    break;
#endif
	  /* CAA extensions */
	  case NG_GO_ARTICLE:
	    np = ng_go_ngptr;
	    goto do_group;
	  /* later: possible go-to-newsgroup (applicable?) */
	}
    }
  loop_break:
    selected_count = save_selected_count;
}

char
multirc_selector()
{
    START_SELECTOR('c');

    sel_rereading = FALSE;
    sel_exclusive = FALSE;
    selected_count = 0;

    set_selector(SM_MULTIRC, 0);

  sel_restart:
    end_char = NewsrcSelCmds[0];
    page_char = NewsrcSelCmds[1];
    sel_mask = MF_SEL;
    extra_commands = multirc_commands;

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;

    sel_display();
    if (sel_input() == 'R')
	goto sel_restart;

    newline();
    if (mousebar_cnt)
	clear_rest();

    if (sel_ret=='\r' || sel_ret=='\n' || sel_ret=='Z' || sel_ret=='\t') {
	MULTIRC* mp;
	NEWSRC* rp;
	PUSH_SELECTOR();
	for (mp = multirc_low(); mp; mp = multirc_next(mp)) {
	    if (mp->flags & MF_SEL) {
		mp->flags &= ~MF_SEL;
		save_selected_count--;
		for (rp = mp->first; rp; rp = rp->next)
		    rp->datasrc->flags &= ~DF_UNAVAILABLE;
		if (use_multirc(mp)) {
		    find_new_groups();
		    do_multirc();
		    unuse_multirc(mp);
		}
		else
		    mp->flags &= ~MF_INCLUDED;
	    }
	}
	POP_SELECTOR();
	goto sel_restart;
    }
    END_SELECTOR();
    return sel_ret;
}

char
newsgroup_selector()
{
    START_SELECTOR('w');

    sel_rereading = FALSE;
    sel_exclusive = FALSE;
    selected_count = 0;

    set_selector(SM_NEWSGROUP, 0);

  sel_restart:
    if (*sel_grp_dmode != 's') {
	NEWSRC* rp;
	for (rp = multirc->first; rp; rp = rp->next) {
	    if ((rp->flags & RF_ACTIVE) && !rp->datasrc->desc_sf.hp) {
		find_grpdesc(rp->datasrc, "control");
#ifdef SUPPORT_NNTP
		if (rp->datasrc->desc_sf.fp)
		    rp->datasrc->flags |= DF_NOXGTITLE; /*$$ ok?*/
		else
		    rp->datasrc->desc_sf.refetch_secs = 0;
#endif
	    }
	}
    }

    end_char = NewsgroupSelCmds[0];
    page_char = NewsgroupSelCmds[1];
    if (sel_rereading) {
	sel_mask = NF_DELSEL;
	sel_page_np = NULL;
    } else
	sel_mask = NF_SEL;
    extra_commands = newsgroup_commands;

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;

    sel_display();
    if (sel_input() == 'R')
	goto sel_restart;

    newline();
    if (mousebar_cnt)
	clear_rest();

    if (sel_ret=='\r' || sel_ret=='\n' || sel_ret=='Z' || sel_ret=='\t'
#ifdef SCAN_ART
     || sel_ret==';'
#endif
    ) {
	NGDATA* np;
	PUSH_SELECTOR();
	for (np = first_ng; np; np = np->next) {
	    if ((np->flags & NF_INCLUDED)
	     && (!selected_count || (np->flags & sel_mask)))
		np->flags |= NF_VISIT;
	    else
		np->flags &= ~NF_VISIT;
	}
	sel_dogroups();
	save_selected_count = selected_count;
	POP_SELECTOR();
	if (multirc)
	    goto sel_restart;
	sel_ret = 'q';
    }
    sel_cleanup();
    END_SELECTOR();
    end_only();
    return sel_ret;
}

char
addgroup_selector(flags)
int flags;
{
    START_SELECTOR('j');

    sel_rereading = FALSE;
    sel_exclusive = FALSE;
    selected_count = 0;

    set_selector(SM_ADDGROUP, 0);

  sel_restart:
    if (*sel_grp_dmode != 's') {
	NEWSRC* rp;
	for (rp = multirc->first; rp; rp = rp->next) {
	    if ((rp->flags & RF_ACTIVE) && !rp->datasrc->desc_sf.hp) {
		find_grpdesc(rp->datasrc, "control");
#ifdef SUPPORT_NNTP
		if (!rp->datasrc->desc_sf.fp)
		    rp->datasrc->desc_sf.refetch_secs = 0;
#endif
	    }
	}
    }

    end_char = AddSelCmds[0];
    page_char = AddSelCmds[1];
    /* Setup for selecting articles to read or set unread */
    if (sel_rereading)
	sel_mask = AGF_DELSEL;
    else
	sel_mask = AGF_SEL;
    sel_page_gp = NULL;
    extra_commands = addgroup_commands;

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;

    sel_display();
    if (sel_input() == 'R')
	goto sel_restart;

    selected_count = 0;
    newline();
    if (mousebar_cnt)
	clear_rest();

    if (sel_ret=='\r' || sel_ret=='\n' || sel_ret=='Z' || sel_ret=='\t') {
	ADDGROUP *gp;
	int i;
	addnewbydefault = ADDNEW_SUB;
	for (gp = first_addgroup, i = 0; gp; gp = gp->next, i++) {
	    if (gp->flags & NF_SEL) {
		gp->flags &= ~NF_SEL;
		get_ng(gp->name,flags);
	    }
	}
	addnewbydefault = 0;
    }
    sel_cleanup();
    END_SELECTOR();
    return sel_ret;
}

char
option_selector()
{
    int i;
    char** vals = INI_VALUES(options_ini);
    START_SELECTOR('l');

    sel_rereading = FALSE;
    sel_exclusive = FALSE;
    selected_count = 0;
    parse_ini_section(nullstr, options_ini);

    set_selector(SM_OPTIONS, 0);

  sel_restart:
    end_char = OptionSelCmds[0];
    page_char = OptionSelCmds[1];
    if (sel_rereading)
	sel_mask = AF_DELSEL;
    else
	sel_mask = AF_SEL;
    sel_page_op = -1;
    extra_commands = option_commands;

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;

    sel_display();
    if (sel_input() == 'R' || sel_ret=='\r' || sel_ret=='\n')
	goto sel_restart;

    selected_count = 0;
    newline();
    if (mousebar_cnt)
	clear_rest();

    if (sel_ret=='Z' || sel_ret=='\t' || sel_ret == 'S') {
	set_options(vals);
	if (sel_ret == 'S')
	    save_options(ini_file);
    }
    for (i = 1; options_ini[i].checksum; i++) {
	if (vals[i]) {
	    if (option_saved_vals[i] && strEQ(vals[i],option_saved_vals[i])) {
		if (option_saved_vals[i] != option_def_vals[i])
		    free(option_saved_vals[i]);
		option_saved_vals[i] = NULL;
	    }
	    free(vals[i]);
	    vals[i] = NULL;
	}
    }
    END_SELECTOR();
    return sel_ret;
}

/* returns a command to do */
static int
univ_read(ui)
UNIV_ITEM* ui;
{
    int exit_code = UR_NORM;
    char ch;

    univ_follow_temp = FALSE;
    if (!ui) {
	printf("NULL UI passed to reader!\n") FLUSH;
	sleep(5);
	return exit_code;
    }
    printf("\n") FLUSH;			/* prepare for output msgs... */
    switch (ui->type) {
      case UN_DEBUG1: {
	char* s;
	s = ui->data.str;
	if (s && *s) {
	    printf("Not implemented yet (%s)\n",s) FLUSH;
	    sleep(5);
	    return exit_code;
	}
	break;
      }
      case UN_TEXTFILE: {
	char* s;
	s = ui->data.textfile.fname;
	if (s && *s) {
	    /* later have some way of getting a return code back */
	    univ_page_file(s);
	}
	break;
      }
      case UN_ARTICLE: {
	int ret;
	NGDATA* np;

	if (in_ng) {
	    /* XXX whine: can't recurse at this time */
	    break;
	}
	if (!ui->data.virt.ng)
	    break;			/* XXX whine */
	np = find_ng(ui->data.virt.ng);

	if (!np) {
	    printf("Universal: newsgroup %s not found!",
		   ui->data.virt.ng) FLUSH;
	    sleep(5);
	    return exit_code;
	}
	set_ng(np);
	if (np != current_ng) {
	    recent_ng = current_ng;
	    current_ng = np;
	}
	ThreadedGroup = (use_threads && !(np->flags & NF_UNTHREADED));
	printf("Virtual: Entering %s:\n", ngname) FLUSH;
	ng_go_artnum = ui->data.virt.num;
	univ_read_virtflag = TRUE;
	ret = do_newsgroup(nullstr);
	univ_read_virtflag = FALSE;
	switch (ret) {
	  case NG_NORM:		/* handle more cases later */
	  case NG_SELNEXT:
	  case NG_NEXT:
	    /* just continue reading */
	    break;
	  case NG_SELPRIOR:
	    /* not implemented yet */
	    /* FALL THROUGH */
	  case NG_ERROR:
	  case NG_ASK:
	    exit_code = UR_BREAK;
	    return exit_code;
	  case NG_MINUS:
	    /* not implemented */
	    break;
	  default:
	    break;
	}
	break;
      }
      case UN_GROUPMASK: {
	univ_mask_load(ui->data.gmask.masklist,ui->data.gmask.title);
	ch = universal_selector();
	switch (ch) {
	  case 'q':
	    exit_code = UR_BREAK;
	    break;
	  default:
	    exit_code = UR_NORM;
	    break;
	}
	return exit_code;
      }
      case UN_CONFIGFILE: {
	univ_file_load(ui->data.cfile.fname,ui->data.cfile.title,
		       ui->data.cfile.label);
	ch = universal_selector();
	switch (ch) {
	  case 'q':
	    exit_code = UR_BREAK;
	    break;
	  default:
	    exit_code = UR_NORM;
	    break;
	}
	return exit_code;
      }
      case UN_NEWSGROUP: {
	int ret;
	NGDATA* np;

	if (in_ng) {
	    /* XXX whine: can't recurse at this time */
	    break;
	}
	if (!ui->data.group.ng)
	    break;			/* XXX whine */
	np = find_ng(ui->data.group.ng);

	if (!np) {
	    printf("Universal: newsgroup %s not found!",
		   ui->data.group.ng) FLUSH;
	    sleep(5);
	    return exit_code;
	}
      do_group:
	set_ng(np);
	if (np != current_ng) {
	    recent_ng = current_ng;
	    current_ng = np;
	}
	ThreadedGroup = (use_threads && !(np->flags & NF_UNTHREADED));
	printf("Entering %s:", ngname) FLUSH;
#ifdef SCAN_ART
	if (sel_ret == ';')
	    ret = do_newsgroup(savestr(";"));
	else
#endif
	    ret = do_newsgroup(nullstr);
	switch (ret) {
	  case NG_NORM:		/* handle more cases later */
	  case NG_SELNEXT:
	  case NG_NEXT:
	    /* just continue reading */
	    break;
	  case NG_SELPRIOR:
	    /* not implemented yet */
	    /* FALL THROUGH */
	  case NG_ERROR:
	  case NG_ASK:
	    exit_code = UR_BREAK;
	    return exit_code;
	  case NG_MINUS:
	    np = recent_ng;
	    goto do_group;
#ifdef SUPPORT_NNTP
	  case NG_NOSERVER:
	    /* Eeep! */
	    break;
#endif
	}
	break;
      }
      case UN_HELPKEY:
	if (another_command(univ_key_help(ui->data.i)))
	    pushchar(sel_ret | 0200);
	break;
      default:
	break;
    }
    return exit_code;
}

char
universal_selector()
{
    START_SELECTOR('v');		/* kind of like 'v'irtual... */

    sel_rereading = FALSE;
    sel_exclusive = FALSE;
    selected_count = 0;

    set_selector(SM_UNIVERSAL, 0);

    selected_count = 0;

sel_restart:
    /* make options */
    end_char = 'Z';
    page_char = '>';

    /* Setup for selecting articles to read or set unread */
    if (sel_rereading)
	sel_mask = UF_DELSEL;
    else
	sel_mask = UF_SEL;
    sel_page_univ = NULL;
    extra_commands = universal_commands;

    init_pages(FILL_LAST_PAGE);
    sel_item_index = 0;

    sel_display();
    if (sel_input() == 'R')
	goto sel_restart;

    newline();
    if (mousebar_cnt)
	clear_rest();

    if (sel_ret=='\r' || sel_ret=='\n' || sel_ret=='\t'
#ifdef SCAN_ART
     || sel_ret==';'
#endif
     || sel_ret=='Z') {
	UNIV_ITEM *ui;
	int i;
	for (ui = first_univ, i = 0; ui; ui = ui->next, i++) {
	    int ret;
	    if (ui->flags & UF_SEL) {
		PUSH_SELECTOR();
		PUSH_UNIV_SELECTOR();

		ui->flags &= ~UF_SEL;
		save_selected_count--;
		ret = univ_read(ui);

		POP_UNIV_SELECTOR();
		POP_SELECTOR();
		if (ret == UR_ERROR || ret == UR_BREAK) {
		    sel_ret = ' ';	/* don't leave completely. */
		    break;		/* jump out of for loop */
		}
	    }
	}
    }
/*univ_loop_break:*/
    /* restart the selector unless the user explicitly quits.
     * possibly later have an option for 'Z' to quit levels>1.
     */
    if (sel_ret != 'q' && (sel_ret != 'Q'))
	goto sel_restart;
    sel_cleanup();
    univ_close();
    END_SELECTOR();
    return sel_ret;
}

static void
sel_display()
{
    /* Present a page of items to the user */
    display_page();
    if (erase_screen && erase_each_line)
	erase_line(1);

    if (sel_item_index >= sel_page_item_cnt)
	sel_item_index = 0;

    if (disp_status_line == 1) {
	newline();
	fputs(msg, stdout);
	term_col = strlen(msg);
	disp_status_line = 2;
    }
}

static void
sel_status_msg(cp)
char* cp;
{
    if (can_home)
	goto_xy(0,sel_last_line+1);
    else
	newline();
    fputs(cp, stdout);
    term_col = strlen(cp);
    goto_xy(0,sel_items[sel_item_index].line);
    fflush(stdout);	/* CAA: otherwise may not be visible */
    disp_status_line = 2;
}

static char
sel_input()
{
    register int j;
    int ch, action;
    char* in_select;
    int got_dash, got_goto;
    int sel_number;
    int ch_num1;

    /* CAA: TRN proudly continues the state machine traditions of RN.
     *      April 2, 1996: 28 gotos in this function.  Conversion to
     *      structured programming is left as an exercise for the reader.
     */
    /* If one immediately types a goto command followed by a dash ('-'),
     * the following will be the default action.
     */
    action = '+';
  reask_selector:
    /* Prompt the user */
    sel_prompt();
  position_selector:
    got_dash = got_goto = 0;
    force_sel_pos = -1;
    if (removed_prompt & 1) {
	draw_mousebar(COLS,0);
	removed_prompt &= ~1;
    }
    if (can_home)
	goto_xy(0,sel_items[sel_item_index].line);
#ifdef USE_TK
    /* Allow Tk to do something with the current positioning */
    if (ttk_running) {
	SEL_UNION u;

	u = sel_items[sel_item_index].u;
	switch (sel_mode) {
	  case SM_THREAD:
	  case SM_SUBJECT:
	    ttcl_eval("wipetree");
	    if (sel_page_item_cnt != 0)
                ttk_draw_tree(u.sp->thread, 0, 0);
	    break;
	  case SM_ARTICLE:
	  case SM_MULTIRC:
	  case SM_ADDGROUP:
	  case SM_NEWSGROUP:
	  case SM_OPTIONS:
	  case SM_UNIVERSAL:
	  default:
	    break;
	}
    }
#endif
reinp_selector:
    if (removed_prompt & 1)
	goto position_selector;	/* (CAA: TRN considered harmful? :-) */
    /* Grab some commands from the user */
    fflush(stdout);
    eat_typeahead();
    if (UseSelNum)
	spin_char = '0' + (sel_item_index+1)/10;	/* first digit */
    else
	spin_char = sel_chars[sel_item_index];
    cache_until_key();
    getcmd(buf);
    if (*buf == ' ')
	setdef(buf, sel_at_end? &end_char : &page_char);
    ch = *buf;
    if (errno)
	ch = Ctl('l');
    if (disp_status_line == 2) {
	if (can_home) {
	    goto_xy(0,sel_last_line+1);
	    erase_line(0);
	    if (term_line == LINES-1)
		removed_prompt |= 1;
	}
	disp_status_line = 0;
    }
    if (ch == '-' && sel_page_item_cnt) {
	got_dash = 1;
	got_goto = 0;	/* right action is not clear if both are true */
	if (can_home) {
	    if (!input_pending()) {
		j = (sel_item_index > 0? sel_item_index : sel_page_item_cnt);
		if (UseSelNum)
		    sprintf(msg,"Range: %d-", j);
		else
		    sprintf(msg,"Range: %c-", sel_chars[j-1]);
		sel_status_msg(msg);
	    }
	}
	else {
	    putchar('-');
	    fflush(stdout);
	}
	goto reinp_selector;
    }
    /* allow the user to back out of a range or a goto with erase char */
    if (ch == ERASECH || ch == KILLCH) {
	/* later consider dingaling() if neither got_{dash,goto} is true */
	got_dash = 0;
	got_goto = 0;
	/* following if statement should be function */
	if (disp_status_line == 2) {	/* status was printed */
	    if (can_home) {
		goto_xy(0,sel_last_line+1);
		erase_line(0);
		if (term_line == LINES-1)
		    removed_prompt |= 1;
	    }
	    disp_status_line = 0;
	}
	goto position_selector;
    }
    if (ch == Ctl('g')) {
	got_goto = 1;
	got_dash = 0;	/* right action is not clear if both are true */
	if (!input_pending()) {
	    if (UseSelNum)
		sel_status_msg("Go to number?");
	    else
		sel_status_msg("Go to letter?");
	}
	goto reinp_selector;
    }
    if (sel_mode == SM_OPTIONS && (ch == '\r' || ch == '\n'))
	ch = '.';
    in_select = index(sel_chars, ch);
    if (UseSelNum && ch >= '0' && ch <= '9') {
	ch_num1 = ch;
	/* would be *very* nice to use wait_key_pause() here */
	if (!input_pending()) {
	    if (got_dash) {
		if (sel_item_index > 0) {
		    j = sel_item_index;  /* -1, +1 to print */
		} else	/* wrap around from the bottom */
		    j = sel_page_item_cnt;
		sprintf(msg,"Range: %d-%c", j, ch);
	    } else {
		if (got_goto) {
		    sprintf(msg,"Go to number: %c", ch);
		} else {
		    sprintf(msg,"%c", ch);
		}
	    }
	    sel_status_msg(msg);
	}	
	/* Consider cache_until_key() here.  The middle of typing a
	 * number is a lousy time to delay, however.
	 */
	getcmd(buf);
	ch = *buf;
	if (disp_status_line == 2) {	/* status was printed */
	    if (can_home) {
		goto_xy(0,sel_last_line+1);
		erase_line(0);
		if (term_line == LINES-1)
		    removed_prompt |= 1;
	    }
	    disp_status_line = 0;
	}
	if (ch == KILLCH) {	/* kill whole command in progress */
	    got_goto = 0;
	    got_dash = 0;
	    goto position_selector;
	}
	if (ch == ERASECH) {
	    /* Erase any first digit printed, but allow complex
	     * commands to continue.  Spaces at end of message are
	     * there to wipe out old first digit.
	     */
	    if (got_dash) {
		if (sel_item_index > 0) {
		    j = sel_item_index;  /* -1, +1 to print */
		} else	/* wrap around from the bottom */
		    j = sel_page_item_cnt;
		sprintf(msg,"Range: %d- ", j);
		sel_status_msg(msg);
		goto reinp_selector;
	    }
	    if (got_goto) {
		sel_status_msg("Go to number?  ");
		goto reinp_selector;
	    }
	    goto position_selector;
	}
	if (ch >= '0' && ch <= '9')
	    sel_number = ((ch_num1 - '0') * 10) + (ch - '0');
	else {
	    pushchar(ch);	/* for later use */
	    sel_number = (ch_num1 - '0');
	}
	j = sel_number-1;
	if ((j < 0) || (j >= sel_page_item_cnt)) {
	    dingaling();
	    sprintf(msg, "No item %c%c on this page.", ch_num1, ch);
	    sel_status_msg(msg);
	    goto position_selector;
	}
	else if (got_goto || (SelNumGoto && !got_dash)) {
	    /* (but only do always-goto if there was not a dash) */
	    sel_item_index = j;
	    goto position_selector;
	} else if (got_dash)
	    ;
	else if (sel_items[j].sel == 1)
	    action = (sel_rereading? 'k' : '-');
	else
	    action = '+';
    } else if (in_select && !UseSelNum) {
	j = in_select - sel_chars;
	if (j >= sel_page_item_cnt) {
	    dingaling();
	    sprintf(msg, "No item '%c' on this page.", ch);
	    sel_status_msg(msg);
	    goto position_selector;
	}
	else if (got_goto) {
	    sel_item_index = j;
	    goto position_selector;
	} else if (got_dash)
	    ;
	else if (sel_items[j].sel == 1)
	    action = (sel_rereading? 'k' : '-');
	else
	    action = '+';
    } else if (ch == '*' && sel_mode == SM_ARTICLE) {
	register ARTICLE* ap;
	if (!sel_page_item_cnt)
	    dingaling();
	else {
	    ap = sel_items[sel_item_index].u.ap;
	    if (sel_items[sel_item_index].sel)
		deselect_subject(ap->subj);
	    else
		select_subject(ap->subj, 0);
	    update_page();
	}
	goto position_selector;
    } else if (ch == 'y' || ch == '.' || ch == '*' || ch == Ctl('t')) {
	j = sel_item_index;
	if (sel_page_item_cnt && sel_items[j].sel == 1)
	    action = (sel_rereading? 'k' : '-');
	else
	    action = '+';
	if (ch == Ctl('t'))
	    force_sel_pos = j;
    } else if (ch == 'k' || ch == 'j' || ch == ',') {
	j = sel_item_index;
	action = 'k';
    } else if (ch == 'm' || ch == '|') {
	j = sel_item_index;
	action = '-';
    } else if (ch == 'M' && in_ng) {
	j = sel_item_index;
	action = 'M';
    } else if (ch == '@') {
	sel_item_index = 0;
	j = sel_page_item_cnt-1;
	got_dash = 1;
	action = '@';
    } else if (ch == '[' || ch == 'p') {
	if (--sel_item_index < 0)
	    sel_item_index = sel_page_item_cnt? sel_page_item_cnt-1 : 0;
	goto position_selector;
    } else if (ch == ']' || ch == 'n') {
	if (++sel_item_index >= sel_page_item_cnt)
	    sel_item_index = 0;
	goto position_selector;
    } else {
	sel_ret = ch;
	switch (sel_command(ch)) {
	  case DS_ASK:
	    if (!clean_screen) {
		sel_display();
		goto reask_selector;
	    }
	    if (removed_prompt & 2) {
		carriage_return();
		goto reask_selector;
	    }
	    goto position_selector;
	  case DS_DISPLAY:
	    sel_display();
	    goto reask_selector;
	  case DS_UPDATE:
	    if (!clean_screen) {
		sel_display();
		goto reask_selector;
	    }
	    if (disp_status_line == 1) {
		newline();
		fputs(msg,stdout);
		term_col = strlen(msg);
		if (removed_prompt & 1) {
		    draw_mousebar(COLS,0);
		    removed_prompt &= ~1;
		}
		disp_status_line = 2;
	    }
	    update_page();
	    if (can_home)
		goto_xy(0,sel_last_line);
	    goto reask_selector;
	  case DS_RESTART:
	    return 'R'; /*Restart*/
	  case DS_QUIT:
	    return 'Q'; /*Quit*/
	  case DS_STATUS:
	    disp_status_line = 1;
	    if (!clean_screen) {
		sel_display();
		goto reask_selector;
	    }
	    sel_status_msg(msg);

	    if (!can_home)
		newline();
	    if (removed_prompt & 2)
		goto reask_selector;
	    goto position_selector;
	}
    }

    if (!sel_page_item_cnt) {
	dingaling();
	goto position_selector;
    }

    /* The user manipulated one of the letters -- handle it. */
    if (!got_dash)
	sel_item_index = j;
    else {
	if (j < sel_item_index) {
	    ch = sel_item_index-1;
	    sel_item_index = j;
	    j = ch;
	}
    }

    if (++j == sel_page_item_cnt)
	j = 0;
    do {
	register int sel = sel_items[sel_item_index].sel;
	if (can_home)
	    goto_xy(0,sel_items[sel_item_index].line);
	if (action == '@') {
	    if (sel == 2)
		ch = (sel_rereading? '+' : ' ');
	    else if (sel_rereading)
		ch = 'k';
	    else if (sel == 1)
		ch = '-';
	    else
		ch = '+';
	} else
	    ch = action;
	switch (ch) {
	  case '+':
	    if (select_item(sel_items[sel_item_index].u))
		output_sel(sel_item_index, 1, TRUE);
	    if (term_line >= sel_last_line) {
		sel_display();
		goto reask_selector;
	    }
	    break;
	  case '-':  case 'k':  case 'M': {
	    bool sel_reread_save = sel_rereading;
	    if (ch == 'M')
		delay_return_item(sel_items[sel_item_index].u);
	    if (ch == '-')
		sel_rereading = FALSE;
	    else
		sel_rereading = TRUE;
	    if (deselect_item(sel_items[sel_item_index].u))
		output_sel(sel_item_index, ch == '-' ? 0 : 2, TRUE);
	    sel_rereading = sel_reread_save;
	    if (term_line >= sel_last_line) {
		sel_display();
		goto reask_selector;
	    }
	    break;
	  }
	}
	if (can_home)
	    carriage_return();
	fflush(stdout);
	if (++sel_item_index == sel_page_item_cnt)
	    sel_item_index = 0;
    } while (sel_item_index != j);
    if (force_sel_pos >= 0)
	sel_item_index = force_sel_pos;
    goto position_selector;
}

static void
sel_prompt()
{
    draw_mousebar(COLS,0);
    if (can_home)
	goto_xy(0,sel_last_line);
#ifdef MAILCALL
    setmail(FALSE);
#endif
    if (sel_at_end)
	sprintf(cmd_buf, "%s [%c%c] --",
		(!sel_prior_obj_cnt? "All" : "Bot"), end_char, page_char);
    else
	sprintf(cmd_buf, "%s%ld%% [%c%c] --",
		(!sel_prior_obj_cnt? "Top " : nullstr),
		(long)((sel_prior_obj_cnt+sel_page_obj_cnt)*100 / sel_total_obj_cnt),
		page_char, end_char);
    interp(buf, sizeof buf, mailcall);
    sprintf(msg, "%s-- %s %s (%s%s order) -- %s", buf,
	    sel_exclusive && in_ng? "SELECTED" : "Select", sel_mode_string,
	    sel_direction<0? "reverse " : nullstr, sel_sort_string, cmd_buf);
    color_string(COLOR_CMD,msg);
    term_col = strlen(msg);
    removed_prompt = 0;
}

static bool
select_item(u)
SEL_UNION u;
{
    switch (sel_mode) {
      case SM_MULTIRC:
	if (!(u.mp->flags & sel_mask))
	    selected_count++;
	u.mp->flags = (u.mp->flags /*& ~MF_DEL*/) | sel_mask;
	break;
      case SM_ADDGROUP:
	if (!(u.gp->flags & sel_mask))
	    selected_count++;
	u.gp->flags = (u.gp->flags & ~AGF_DEL) | sel_mask;
	break;
      case SM_NEWSGROUP:
	if (!(u.np->flags & sel_mask))
	    selected_count++;
	u.np->flags = (u.np->flags & ~NF_DEL) | sel_mask;
	break;
      case SM_OPTIONS:
	if (!select_option(u.op) || !INI_VALUE(options_ini,u.op))
	    return FALSE;
	break;
      case SM_THREAD:
	select_thread(u.sp->thread, 0);
	break;
      case SM_SUBJECT:
	select_subject(u.sp, 0);
	break;
      case SM_ARTICLE:
	select_article(u.ap, 0);
	break;
      case SM_UNIVERSAL:
	if (!(u.un->flags & sel_mask))
	    selected_count++;
	u.un->flags = (u.un->flags & ~UF_DEL) | sel_mask;
	break;
    }
    return TRUE;
}

static bool
delay_return_item(u)
SEL_UNION u;
{
    switch (sel_mode) {
      case SM_MULTIRC:
      case SM_ADDGROUP:
      case SM_NEWSGROUP:
      case SM_OPTIONS:
      case SM_UNIVERSAL:
	return FALSE;
      case SM_ARTICLE:
	delay_unmark(u.ap);
	break;
      default: {
	  register ARTICLE* ap;
	  if (sel_mode == SM_THREAD) {
	      for (ap = first_art(u.sp); ap; ap = next_art(ap))
		  if (!!(ap->flags & AF_UNREAD) ^ sel_rereading)
		      delay_unmark(ap);
	  } else {
	      for (ap = u.sp->articles; ap; ap = ap->subj_next)
		  if (!!(ap->flags & AF_UNREAD) ^ sel_rereading)
		      delay_unmark(ap);
	  }
	  break;
      }
    }
    return TRUE;
}

static bool
deselect_item(u)
SEL_UNION u;
{
    switch (sel_mode) {
      case SM_MULTIRC:
	if (u.mp->flags & sel_mask) {
	    u.mp->flags &= ~sel_mask;
	    selected_count--;
	}
#if 0
	if (sel_rereading)
	    u.mp->flags |= MF_DEL;
#endif
	break;
      case SM_ADDGROUP:
	if (u.gp->flags & sel_mask) {
	    u.gp->flags &= ~sel_mask;
	    selected_count--;
	}
	if (sel_rereading)
	    u.gp->flags |= AGF_DEL;
	break;
      case SM_NEWSGROUP:
	if (u.np->flags & sel_mask) {
	    u.np->flags &= ~sel_mask;
	    selected_count--;
	}
	if (sel_rereading)
	    u.np->flags |= NF_DEL;
	break;
      case SM_OPTIONS:
	if (!select_option(u.op) || INI_VALUE(options_ini,u.op))
	    return FALSE;
	break;
      case SM_THREAD:
	deselect_thread(u.sp->thread);
	break;
      case SM_SUBJECT:
	deselect_subject(u.sp);
	break;
      case SM_UNIVERSAL:
	if (u.un->flags & sel_mask) {
	    u.un->flags &= ~sel_mask;
	    selected_count--;
	}
	if (sel_rereading)
	    u.un->flags |= UF_DEL;
	break;
      default:
	deselect_article(u.ap, 0);
	break;
    }
    return TRUE;
}

static bool
select_option(i)
int i;
{
    bool changed = FALSE;
    char** vals = INI_VALUES(options_ini);
    char* val;
    char* oldval;

    if (*options_ini[i].item == '*') {
	option_flags[i] ^= OF_SEL;
	init_pages(FILL_LAST_PAGE);
	term_line = sel_last_line;
	return FALSE;
    }

    goto_xy(0,sel_last_line);
    erase_line(mousebar_cnt > 0);	/* erase the prompt */
    color_object(COLOR_CMD, 1);
    printf("Change `%s' (%s)",options_ini[i].item, options_ini[i].help_str);
    color_pop();	/* of COLOR_CMD */
    newline();
    *buf = '\0';
    oldval = savestr(quote_string(option_value(i)));
    val = vals[i]? vals[i] : oldval;
    clean_screen = in_choice("> ", val, options_ini[i].help_str, 'z');
    if (strNE(buf,val)) {
	char* to = buf;
	char* from = buf;
	parse_string(&to, &from);
	changed = TRUE;
	if (vals[i]) {
	    free(vals[i]);
	    selected_count--;
	}
	if (val != oldval && strEQ(buf,oldval))
	    vals[i] = NULL;
	else {
	    vals[i] = savestr(buf);
	    selected_count++;
	}
    }
    free(oldval);
    if (clean_screen) {
	up_line();
	erase_line(1);
	sel_prompt();
	goto_xy(0,sel_items[sel_item_index].line);
	if (changed) {
	    erase_line(0);
	    display_option(i,sel_item_index);
	    up_line();
	}
    }
    else
	return FALSE;
    return TRUE;
}

static void
sel_cleanup()
{
    switch (sel_mode) {
      case SM_MULTIRC:
	break;
      case SM_ADDGROUP:
	if (sel_rereading) {
	    ADDGROUP* gp;
	    for (gp = first_addgroup; gp; gp = gp->next) {
		if (gp->flags & AGF_DELSEL) {
		    if (!(gp->flags & AGF_SEL))
			selected_count++;
		    gp->flags = (gp->flags&~(AGF_DELSEL|AGF_EXCLUDED))|AGF_SEL;
		}
		gp->flags &= ~AGF_DEL;
	    }
	}
	else {
	    ADDGROUP* gp;
	    for (gp = first_addgroup; gp; gp = gp->next) {
		if (gp->flags & AGF_DEL)
		    gp->flags = (gp->flags & ~AGF_DEL) | AGF_EXCLUDED;
	    }
	}
	break;
      case SM_NEWSGROUP:
	if (sel_rereading) {
	    NGDATA* np;
	    for (np = first_ng; np; np = np->next) {
		if (np->flags & NF_DELSEL) {
		    if (!(np->flags & NF_SEL))
			selected_count++;
		    np->flags = (np->flags & ~NF_DELSEL) | NF_SEL;
		}
		np->flags &= ~NF_DEL;
	    }
	}
	else {
	    NGDATA* np;
	    for (np = first_ng; np; np = np->next) {
		if (np->flags & NF_DEL) {
		    np->flags &= ~NF_DEL;
		    catch_up(np, 0, 0);
		}
	    }
	}
	break;
      case SM_OPTIONS:
	break;
      /* should probably be expanded... */
      case SM_UNIVERSAL:
	break;
      default:
	if (sel_rereading) {
	    /* Turn selections into unread selected articles.  Let
	    ** count_subjects() fix the counts after we're through.
	    */
	    register SUBJECT* sp;
	    sel_last_ap = NULL;
	    sel_last_sp = NULL;
	    for (sp = first_subject; sp; sp = sp->next)
		unkill_subject(sp);
	}
	else {
	    if (sel_mode == SM_ARTICLE)
		article_walk(mark_DEL_as_READ, 0);
	    else {
		register SUBJECT* sp;
		for (sp = first_subject; sp; sp = sp->next) {
		    if (sp->flags & SF_DEL) {
			sp->flags &= ~SF_DEL;
			if (sel_mode == SM_THREAD)
			    kill_thread(sp->thread, AFFECT_UNSEL);
			else
			    kill_subject(sp, AFFECT_UNSEL);
		    }
		}
	    }
	}
	break;
    }
}

static bool
mark_DEL_as_READ(ptr, arg)
char* ptr;
int arg;
{
    register ARTICLE* ap = (ARTICLE*)ptr;
    if (ap->flags & AF_DEL) {
	ap->flags &= ~AF_DEL;
	set_read(ap);
    }
    return 0;
}

static int
sel_command(ch)
char_int ch;
{
    int ret;
    if (can_home)
	goto_xy(0,sel_last_line);
    clean_screen = TRUE;
    term_scrolled = 0;
    page_line = 1;
    if (sel_mode == SM_NEWSGROUP) {
	if (sel_item_index < sel_page_item_cnt)
	    set_ng(sel_items[sel_item_index].u.np);
	else
	    ngptr = NULL;
    }
  do_command:
    *buf = ch;
    buf[1] = FINISHCMD;
    output_chase_phrase = TRUE;
    switch (ch) {
      case '>':
	sel_item_index = 0;
	if (next_page())
	    return DS_DISPLAY;
	break;
      case '<':
	sel_item_index = 0;
	if (prev_page())
	    return DS_DISPLAY;
	break;
      case '^':  case Ctl('r'):
	sel_item_index = 0;
	if (first_page())
	    return DS_DISPLAY;
	break;
      case '$':
	sel_item_index = 0;
	if (last_page())
	    return DS_DISPLAY;
	break;
      case Ctl('l'):
	return DS_DISPLAY;
      case Ctl('^'):
	erase_line(0);		/* erase the prompt */
	removed_prompt |= 2;
#ifdef MAILCALL
	setmail(TRUE);		/* force a mail check */
#endif
	break;
      case '\r':  case '\n':
	if (!selected_count && sel_page_item_cnt) {
	    if (sel_rereading || sel_items[sel_item_index].sel != 2)
		select_item(sel_items[sel_item_index].u);
	}
	return DS_QUIT;
      case 'Z':  case '\t':
	return DS_QUIT;
      case 'q':  case 'Q':  case '+':  case '`':
	return DS_QUIT;
      case Ctl('Q'):  case '\033':
	sel_ret = '\033';
	return DS_QUIT;
      case '\b':  case '\177':
	return DS_QUIT;
      case Ctl('k'):
	edit_kfile();
	return DS_DISPLAY;
      case '&':  case '!':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (!finish_command(TRUE)) {	/* get rest of command */
	    if (clean_screen)
		break;
	}
	else {
	    PUSH_SELECTOR();
	    one_command = TRUE;
	    perform(buf, FALSE);
	    one_command = FALSE;
	    if (term_line != sel_last_line+1 || term_scrolled)
		clean_screen = FALSE;
	    POP_SELECTOR();
	    if (!save_sel_mode)
		return DS_RESTART;
	    if (clean_screen) {
		erase_line(0);
		return DS_ASK;
	    }
	}
	if ((ch = another_command(1)) != '\0')
	    goto do_command;
	return DS_DISPLAY;
      case 'v':
	newline();
	trn_version();
	if ((ch = another_command(1)) != '\0')
	    goto do_command;
	return DS_DISPLAY;
      case '\\':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (sel_mode == SM_NEWSGROUP)
	    printf("[%s] Cmd: ", ngptr? ngptr->rcline : "*End*");
	else
	    fputs("Cmd: ", stdout);
	fflush(stdout);
	getcmd(buf);
	if (*buf == '\\')
	    goto the_default;
	if (*buf != ' ' && *buf != '\n' && *buf != '\r') {
	    ch = *buf;
	    goto do_command;
	}
	if (clean_screen) {
	    erase_line(0);
	    break;
	}
	if ((ch = another_command(1)) != '\0')
	    goto do_command;
	return DS_DISPLAY;
      default:
      the_default:
	ret = extra_commands(ch);
	switch (ret) {
	  case DS_ERROR:
	    break;
	  case DS_DOCOMMAND:
	    ch = sel_ret;
	    goto do_command;
	  default:
	    return ret;
	}
	strcpy(msg,"Type ? for help.");
	settle_down();
	if (clean_screen)
	    return DS_STATUS;
	printf("\n%s\n",msg);
	if ((ch = another_command(1)) != '\0')
	    goto do_command;
	return DS_DISPLAY;
    }
    return DS_ASK;
}

static bool
sel_perform_change(cnt, obj_type)
long cnt;
char* obj_type;
{
    int ret;

    carriage_return();
    if (page_line == 1) {
	disp_status_line = 1;
	if (term_line != sel_last_line+1 || term_scrolled)
	    clean_screen = FALSE;
    }
    else
	clean_screen = FALSE;

    if (error_occurred) {
	print_lines(msg,NOMARKING);
	clean_screen = error_occurred = FALSE;
    }

    ret = perform_status_end(cnt, obj_type);
    if (ret)
	disp_status_line = 1; 
    if (clean_screen) {
	if (ret != 2) {
	    up_line();
	    return TRUE;
	}
    }
    else if (disp_status_line == 1) {
	print_lines(msg,NOMARKING);
	disp_status_line = 0;
    }

    init_pages(PRESERVE_PAGE);

    return FALSE;
}

#ifdef SCAN_ART
#define SPECIAL_CMD_LETTERS "<+>^$!?&:/\\hDEJLNOPqQRSUXYZ\n\r\t\033;"
#else
#define SPECIAL_CMD_LETTERS "<+>^$!?&:/\\hDEJLNOPqQRSUXYZ\n\r\t\033"
#endif

static char
another_command(ch)
char_int ch;
{
    bool skip_q = !ch;
    if (ch < 0)
	return 0;
    if (ch > 1) {
	read_tty(buf,1);
	ch = *buf;
    }
    else
	ch = pause_getcmd();
    if (ch != 0 && ch != '\n' && ch != '\r' && (!skip_q || ch != 'q')) {
	if (ch > 0) {
	    /* try to optimize the screen update for some commands. */
	    if (!index(sel_chars, ch)
	     && (index(SPECIAL_CMD_LETTERS, ch) || ch == Ctl('k'))) {
		sel_ret = ch;
		return ch;
	    }
	    pushchar(ch | 0200);
	}
    }
    return '\0';
}

static int
article_commands(ch)
char_int ch;
{
    switch (ch) {
      case 'U':
	sel_cleanup();
	sel_rereading = !sel_rereading;
	sel_page_sp = NULL;
	sel_page_app = NULL;
	if (!cache_range(sel_rereading? absfirst : firstart, lastart))
	    sel_rereading = !sel_rereading;
	return DS_RESTART;
      case '#':
	if (sel_page_item_cnt) {
	    register SUBJECT* sp;
	    for (sp = first_subject; sp; sp = sp->next)
		sp->flags &= ~SF_SEL;
	    selected_count = 0;
	    deselect_item(sel_items[sel_item_index].u);
	    select_item(sel_items[sel_item_index].u);
	    if (!keep_the_group_static)
		keep_the_group_static = 2;
	}
	return DS_QUIT;
      case 'N':  case 'P':
	return DS_QUIT;
      case 'L':
	switch_dmode(&sel_art_dmode);	    /* sets msg */
	return DS_DISPLAY;
      case 'Y':
	if (!dmcount) {
	    strcpy(msg,"No marked articles to yank back.");
	    return DS_STATUS;
	}
	yankback();
	if (!sel_rereading)
	    sel_cleanup();
	disp_status_line = 1;
	count_subjects(CS_NORM);
	sel_page_sp = NULL;
	sel_page_app = NULL;
	init_pages(PRESERVE_PAGE);
	return DS_DISPLAY;
      case '=':
	if (!sel_rereading)
	    sel_cleanup();
	if (sel_mode == SM_ARTICLE) {
	    set_selector(sel_threadmode, 0);
	    sel_page_sp = sel_page_app? sel_page_app[0]->subj : NULL;
	} else {
	    set_selector(SM_ARTICLE, 0);
	    sel_page_app = 0;
	}
	count_subjects(CS_NORM);
	sel_item_index = 0;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'S':
	if (!sel_rereading)
	    sel_cleanup();
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
      reask_output:
	in_char("Selector mode:  Threads, Subjects, Articles?", 'o', "tsa");
#ifdef VERIFY
	printcmd();
#endif
	if (*buf == 'h' || *buf == 'H') {
#ifdef VERBOSE
	    IF(verbose)
		fputs("\n\
Type t or SP to display/select thread groups (threads the group, if needed).\n\
Type s to display/select subject groups.\n\
Type a to display/select individual articles.\n\
Type q to leave things as they are.\n\n\
",stdout) FLUSH;
	    ELSE
#endif
#ifdef TERSE
		fputs("\n\
t or SP selects thread groups (threads the group too).\n\
s selects subject groups.\n\
a selects individual articles.\n\
q does nothing.\n\n\
",stdout) FLUSH;
#endif
	    clean_screen = FALSE;
	    goto reask_output;
	} else if (*buf == 'q') {
	    if (can_home)
		erase_line(0);
	    return DS_ASK;
	}
	if (isupper(*buf))
	    *buf = tolower(*buf);
	set_sel_mode(*buf);
	count_subjects(CS_NORM);
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'O':
	if (!sel_rereading)
	    sel_cleanup();
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
      reask_sort:
	if (sel_mode == SM_ARTICLE)
	    in_char(
	       "Order by Date,Subject,Author,Number,subj-date Groups,Points?",
		    'q', "dsangpDSANGP");
	else
	    in_char("Order by Date, Subject, Count, Lines, or Points?",
		    'q', "dsclpDSCLP");
#ifdef VERIFY
	printcmd();
#endif
	if (*buf == 'h' || *buf == 'H') {
#ifdef VERBOSE
	    IF(verbose) {
		fputs("\n\
Type d or SP to order the displayed items by date.\n\
Type s to order the items by subject.\n\
Type p to order the items by score points.\n\
",stdout) FLUSH;
		if (sel_mode == SM_ARTICLE)
		    fputs("\
Type a to order the items by author.\n\
Type n to order the items by number.\n\
Type g to order the items in subject-groups by date.\n\
",stdout) FLUSH;
		else
		    fputs("\
Type c to order the items by count.\n\
",stdout) FLUSH;
		fputs("\
Typing your selection in upper case it will reverse the selected order.\n\
Type q to leave things as they are.\n\n\
",stdout) FLUSH;
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		fputs("\n\
d or SP sorts by date.\n\
s sorts by subject.\n\
p sorts by points.\n\
",stdout) FLUSH;
		if (sel_mode == SM_ARTICLE)
		    fputs("\
a sorts by author.\n\
g sorts in subject-groups by date.\n\
",stdout) FLUSH;
		else
		    fputs("\
c sorts by count.\n\
",stdout) FLUSH;
		fputs("\
Upper case reverses the sort.\n\
q does nothing.\n\n\
",stdout) FLUSH;
	    }
#endif
	    clean_screen = FALSE;
	    goto reask_sort;
	} else if (*buf == 'q') {
	    if (can_home)
		erase_line(0);
	    return DS_ASK;
	}
	set_sel_sort(sel_mode,*buf);
	count_subjects(CS_NORM);
	sel_page_sp = NULL;
	sel_page_app = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'R':
	if (!sel_rereading)
	    sel_cleanup();
	set_selector(sel_mode, sel_sort * -sel_direction);
	count_subjects(CS_NORM);
	sel_page_sp = NULL;
	sel_page_app = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	count_subjects(CS_NORM);
	sel_page_sp = NULL;
	sel_page_app = NULL;
	init_pages(FILL_LAST_PAGE);
	sel_item_index = 0;
	return DS_DISPLAY;
      case 'X':  case 'D':  case 'J':
	if (!sel_rereading) {
	    if (sel_mode == SM_ARTICLE) {
		register ARTICLE* ap;
		register ARTICLE** app;
		register ARTICLE** limit;
		limit = artptr_list + artptr_list_size;
		if (ch == 'D')
		    app = sel_page_app;
		else
		    app = artptr_list;
		while (app < limit) {
		    ap = *app;
		    if ((!(ap->flags & AF_SEL) ^ (ch == 'J'))
		     || (ap->flags & AF_DEL))
			if (ch == 'J' || !sel_exclusive
			 || (ap->flags & AF_INCLUDED)) {
			    set_read(ap);
			}
		    app++;
		    if (ch == 'D' && app == sel_next_app)
			break;
		}
	    } else {
		register SUBJECT* sp;
		if (ch == 'D')
		    sp = sel_page_sp;
		else
		    sp = first_subject;
		while (sp) {
		    if (((!(sp->flags & SF_SEL) ^ (ch == 'J')) && sp->misc)
		     || (sp->flags & SF_DEL)) {
			if (ch == 'J' || !sel_exclusive
			 || (sp->flags & SF_INCLUDED)) {
			    kill_subject(sp, ch=='J'? AFFECT_ALL:AFFECT_UNSEL);
			}
		    }
		    sp = sp->next;
		    if (ch == 'D' && sp == sel_next_sp)
			break;
		}
	    }
	    count_subjects(CS_UNSELECT);
	    if (obj_count && (ch == 'J' || (ch == 'D' && !selected_count))) {
		init_pages(FILL_LAST_PAGE);
		sel_item_index = 0;
		return DS_DISPLAY;
	    }
	    if (artptr_list && obj_count)
		sort_articles();
	} else if (ch == 'J') {
	    register SUBJECT* sp;
	    for (sp = first_subject; sp; sp = sp->next)
		deselect_subject(sp);
	    selected_subj_cnt = selected_count = 0;
	    return DS_DISPLAY;
	}
	return DS_QUIT;
      case 'T':
	if (!ThreadedGroup) {
	    strcpy(msg,"Group is not threaded.");
	    return DS_STATUS;
	}
	/* FALL THROUGH */
      case 'A':
	if (!sel_page_item_cnt) {
	    dingaling();
	    break;
	}
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (sel_mode == SM_ARTICLE)
	    artp = sel_items[sel_item_index].u.ap;
	else {
	    register SUBJECT* sp = sel_items[sel_item_index].u.sp;
	    if (sel_mode == SM_THREAD) {
		while (!sp->misc)
		    sp = sp->thread_link;
	    }
	    artp = sp->articles;
	}
	art = article_num(artp);
	/* This call executes the action too */
	switch (ask_memorize(ch)) {
	  case 'J': case 'j': case 'K':  case ',':
	    count_subjects(sel_rereading? CS_NORM : CS_UNSELECT);
	    init_pages(PRESERVE_PAGE);
	    strcpy(msg,"Kill memorized.");
	    disp_status_line = 1;
	    return DS_DISPLAY;
	  case '+': case '.': case 'S': case 'm':
	    strcpy(msg,"Selection memorized.");
	    disp_status_line = 1;
	    return DS_UPDATE;
	  case 'c':  case 'C':
	    strcpy(msg,"Auto-commands cleared.");
	    disp_status_line = 1;
	    return DS_UPDATE;
	  case 'q':
	    return DS_UPDATE;
	  case 'Q':
	    break;
	}
	if (can_home)
	    erase_line(0);
	break;
#ifdef SCAN_ART
      case ';':
	sel_ret = ';';
	return DS_QUIT;
#endif
      case ':':
	if (sel_page_item_cnt) {
	    if (sel_mode == SM_ARTICLE)
		artp = sel_items[sel_item_index].u.ap;
	    else {
		register SUBJECT* sp = sel_items[sel_item_index].u.sp;
		if (sel_mode == SM_THREAD) {
		    while (!sp->misc)
			sp = sp->thread_link;
		}
		artp = sp->articles;
	    }
	    art = article_num(artp);
	}
	else
	    art = 0;
	/* FALL THROUGH */
      case '/':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (!finish_command(TRUE)) {	/* get rest of command */
	    if (clean_screen)
		break;
	}
	else {
	    if (ch == ':') {
		thread_perform();
		if (!sel_rereading) {
		    register SUBJECT* sp;
		    for (sp = first_subject; sp; sp = sp->next) {
			if (sp->flags & SF_DEL) {
			    sp->flags = 0;
			    if (sel_mode == SM_THREAD)
				kill_thread(sp->thread, AFFECT_UNSEL);
			    else
				kill_subject(sp, AFFECT_UNSEL);
			}
		    }
		}
	    } else {
		/* Force the search to begin at absfirst or firstart,
		** depending upon whether they specified the 'r' option.
		*/
		art = lastart+1;
		switch (art_search(buf, sizeof buf, FALSE)) {
		  case SRCH_ERROR:
		  case SRCH_ABORT:
		    break;
		  case SRCH_INTR:
		    errormsg("Interrupted");
		    break;
		  case SRCH_DONE:
		  case SRCH_SUBJDONE:
		  case SRCH_FOUND:
		    break;
		  case SRCH_NOTFOUND:
		    errormsg("Not found.");
		    break;
		}
	    }
	    sel_item_index = 0;
	    /* Recount, in case something has changed. */
	    count_subjects(sel_rereading? CS_NORM : CS_UNSELECT);

	    if (sel_perform_change(ngptr->toread, "article"))
		return DS_UPDATE;
	    if (clean_screen)
		return DS_DISPLAY;
	}
	if (another_command(1))
	    return DS_DOCOMMAND;
	return DS_DISPLAY;
      case 'c':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if ((ch = ask_catchup()) == 'y' || ch == 'u') {
	    count_subjects(CS_UNSELECT);
	    if (ch != 'u' && obj_count) {
		sel_page_sp = NULL;
		sel_page_app = NULL;
		init_pages(FILL_LAST_PAGE);
		return DS_DISPLAY;
	    }
	    sel_ret = 'Z';
	    return DS_QUIT;
	}
	if (ch != 'N')
	    return DS_DISPLAY;
	if (can_home)
	    erase_line(0);
	break;
      case 'h':
      case '?':
	univ_help(UHELP_ARTSEL);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_artsel()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
      default:
	return DS_ERROR;
    }
    return DS_ASK;
}

static int
newsgroup_commands(ch)
char_int ch;
{
    switch (ch) {
      case Ctl('n'):
      case Ctl('p'):
	return DS_QUIT;
      case 'U':
	sel_cleanup();
	sel_rereading = !sel_rereading;
	sel_page_np = NULL;
	return DS_RESTART;
      case 'L':
	switch_dmode(&sel_grp_dmode);	    /* sets msg */
	if (*sel_grp_dmode != 's' && !multirc->first->datasrc->desc_sf.hp) {
	    newline();
	    return DS_RESTART;
	}
	return DS_DISPLAY;
      case 'X':  case 'D':  case 'J':
	if (!sel_rereading) {
	    register NGDATA* np;
	    if (ch == 'D')
		np = sel_page_np;
	    else
		np = first_ng;
	    while (np) {
		if (((!(np->flags&NF_SEL) ^ (ch=='J')) && np->toread!=TR_UNSUB)
		 || (np->flags & NF_DEL)) {
		    if (ch == 'J' || (np->flags & NF_INCLUDED))
			catch_up(np, 0, 0);
		    np->flags &= ~(NF_DEL|NF_SEL);
		}
		np = np->next;
		if (ch == 'D' && np == sel_next_np)
		    break;
	    }
	    if (ch == 'J' || (ch == 'D' && !selected_count)) {
		init_pages(FILL_LAST_PAGE);
		if (sel_total_obj_cnt) {
		    sel_item_index = 0;
		    return DS_DISPLAY;
		}
	    }
	} else if (ch == 'J') {
	    register NGDATA* np;
	    for (np = first_ng; np; np = np->next)
		np->flags &= ~NF_DELSEL;
	    selected_count = 0;
	    return DS_DISPLAY;
	}
	return DS_QUIT;
      case '=': {
	NGDATA* np;
	sel_cleanup();
	missing_count = 0;
	for (np = first_ng; np; np = np->next) {
	    if (np->toread > TR_UNSUB && np->toread < ng_min_toread)
		newsgroup_toread++;
	    np->abs1st = 0;
	}
	erase_line(0);
#ifdef SUPPORT_NNTP
	check_active_refetch(TRUE);
#endif
	return DS_RESTART;
      }
      case 'O':
	if (!sel_rereading)
	    sel_cleanup();
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
      reask_sort:
	in_char("Order by Newsrc, Group name, or Count?", 'q', "ngcNGC");
#ifdef VERIFY
	printcmd();
#endif
	switch (*buf) {
	  case 'n': case 'N':
	    break;
	  case 'g': case 'G':
	    *buf += 's' - 'g';		/* Group name == SS_STRING */
	    break;
	  case 'c': case 'C':
	    break;
	  case 'h': case 'H':
#ifdef VERBOSE
	    IF(verbose) {
		fputs("\n\
Type n or SP to order the newsgroups in the .newsrc order.\n\
Type g to order the items by group name.\n\
Type c to order the items by count.\n\
",stdout) FLUSH;
		fputs("\
Typing your selection in upper case it will reverse the selected order.\n\
Type q to leave things as they are.\n\n\
",stdout) FLUSH;
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		fputs("\n\
n or SP sorts by .newsrc.\n\
g sorts by group name.\n\
c sorts by count.\n\
",stdout) FLUSH;
		fputs("\
Upper case reverses the sort.\n\
q does nothing.\n\n\
",stdout) FLUSH;
	    }
#endif
	    clean_screen = FALSE;
	    goto reask_sort;
	  default:
	    if (can_home)
		erase_line(0);
	    return DS_ASK;
	}
	set_sel_sort(sel_mode,*buf);
	sel_page_np = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'R':
	if (!sel_rereading)
	    sel_cleanup();
	set_selector(sel_mode, sel_sort * -sel_direction);
	sel_page_np = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	sel_page_np = NULL;
	init_pages(FILL_LAST_PAGE);
	sel_item_index = 0;
	return DS_DISPLAY;
      case ':':
#if 0
	if (ngptr != current_ng) {
	    recent_ng = current_ng;
	    current_ng = ngptr;
	}
	/* FALL THROUGH */
#endif
      case '/':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (!finish_command(TRUE)) {	/* get rest of command */
	    if (clean_screen)
		break;
	}
	else {
	    if (ch == ':') {
		ngsel_perform();
	    } else {
#ifdef NGSEARCH
		ngptr = NULL;
		switch (ng_search(buf,FALSE)) {
		  case NGS_ERROR:
		  case NGS_ABORT:
		    break;
		  case NGS_INTR:
		    errormsg("Interrupted");
		    break;
		  case NGS_FOUND:
		  case NGS_NOTFOUND:
		  case NGS_DONE:
		    break;
		}
		ngptr = current_ng;
#else
		notincl("/");
#endif
	    }
	    sel_item_index = 0;

	    if (sel_perform_change(newsgroup_toread, "newsgroup"))
		return DS_UPDATE;
	    if (clean_screen)
		return DS_DISPLAY;
	}
	if (another_command(1))
	    return DS_DOCOMMAND;
	return DS_DISPLAY;
      case 'c':
	if (sel_item_index < sel_page_item_cnt)
	    set_ng(sel_items[sel_item_index].u.np);
	else {
	    strcpy(msg, "No newsgroup to catchup.");
	    disp_status_line = 1;
	    return DS_UPDATE;
	}
	if (ngptr != current_ng) {
	    recent_ng = current_ng;
	    current_ng = ngptr;
	}
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if ((ch = ask_catchup()) == 'y' || ch == 'u')
	    return DS_DISPLAY;
	if (ch != 'N')
	    return DS_DISPLAY;
	if (can_home)
	    erase_line(0);
	break;
      case 'h':
      case '?':
	univ_help(UHELP_NGSEL);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_ngsel()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
#ifdef SCAN_ART
      case ';':
	sel_ret = ';';
	return DS_QUIT;
#endif
      default: {
	SEL_UNION u;
	int ret;
	bool was_at_top = !sel_prior_obj_cnt;
	PUSH_SELECTOR();
	if (!(removed_prompt & 2)) {
	    erase_line(mousebar_cnt > 0);	/* erase the prompt */
	    removed_prompt = 3;
	    printf("[%s] Cmd: ", ngptr? ngptr->rcline : "*End*");
	    fflush(stdout);
	}
	dfltcmd = "\\";
	set_mode('r','n');
	if (ch == '\\') {
	    putchar(ch);
	    fflush(stdout);
	}
	else
	    pushchar(ch | 0200);
	do {
	    ret = input_newsgroup();
	} while (ret == ING_INPUT);
	set_mode('s','w');
	POP_SELECTOR();
	switch (ret) {
#ifdef SUPPORT_NNTP
	  case ING_NOSERVER:
	    if (multirc) {
		if (!was_at_top)
		    (void) first_page();
		return DS_RESTART;
	    }
	    /* FALL THROUGH */
#endif
	  case ING_QUIT:
	    sel_ret = 'q';
	    return DS_QUIT;
	  case ING_ERROR:
	    return DS_ERROR;
	  case ING_ERASE:
	    if (clean_screen) {
		erase_line(0);
		return DS_ASK;
	    }
	    break;
	  default:
	    if (!save_sel_mode)
		return DS_RESTART;
	    if (term_line == sel_last_line)
		newline();
	    if (term_line != sel_last_line+1 || term_scrolled)
		clean_screen = FALSE;
	    break;
	}
	sel_item_index = 0;
	init_pages(PRESERVE_PAGE);
	if (ret == ING_SPECIAL && ngptr && ngptr->toread < ng_min_toread){
	    ngptr->flags |= NF_INCLUDED;
	    sel_total_obj_cnt++;
	    ret = ING_DISPLAY;
	}
	u.np = ngptr;
	if ((calc_page(u) || ret == ING_DISPLAY) && clean_screen)
	    return DS_DISPLAY;
	if (ret == ING_MESSAGE) {
	    clean_screen = 0;
	    return DS_STATUS;
	}
	if (was_at_top)
	    (void) first_page();
	if (clean_screen)
	    return DS_ASK;
	newline();
	if (another_command(1))
	    return DS_DOCOMMAND;
	return DS_DISPLAY;
      }
    }
    return DS_ASK;
}

static int
addgroup_commands(ch)
char_int ch;
{
    switch (ch) {
      case 'O':
	if (!sel_rereading)
	    sel_cleanup();
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
      reask_sort:
	in_char("Order by Natural-order, Group name, or Count?", 'q', "ngcNGC");
#ifdef VERIFY
	printcmd();
#endif
	switch (*buf) {
	  case 'n': case 'N':
	    break;
	  case 'g': case 'G':
	    *buf += 's' - 'g';		/* Group name == SS_STRING */
	    break;
	  case 'c': case 'C':
	    break;
	  case 'h': case 'H':
#ifdef VERBOSE
	    IF(verbose) {
		fputs("\n\
Type n or SP to order the items in their naturally occurring order.\n\
Type g to order the items by newsgroup name.\n\
Type c to order the items by article count.\n\
",stdout) FLUSH;
		fputs("\
Typing your selection in upper case it will reverse the selected order.\n\
Type q to leave things as they are.\n\n\
",stdout) FLUSH;
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		fputs("\n\
n or SP sorts by natural order.\n\
g sorts by newsgroup name.\n\
c sorts by article count.\n\
",stdout) FLUSH;
		fputs("\
Upper case reverses the sort.\n\
q does nothing.\n\n\
",stdout) FLUSH;
	    }
#endif
	    clean_screen = FALSE;
	    goto reask_sort;
	  default:
	    if (can_home)
		erase_line(0);
	    return DS_ASK;
	}
	set_sel_sort(sel_mode,*buf);
	sel_page_np = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'U':
	sel_cleanup();
	sel_rereading = !sel_rereading;
	sel_page_gp = NULL;
	return DS_RESTART;
      case 'R':
	if (!sel_rereading)
	    sel_cleanup();
	set_selector(sel_mode, sel_sort * -sel_direction);
	sel_page_gp = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	sel_page_gp = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'L':
	switch_dmode(&sel_grp_dmode);	    /* sets msg */
	if (*sel_grp_dmode != 's' && !datasrc->desc_sf.hp) {
	    newline();
	    return DS_RESTART;
	}
	return DS_DISPLAY;
      case 'X':  case 'D':  case 'J':
	if (!sel_rereading) {
	    register ADDGROUP* gp;
	    if (ch == 'D')
		gp = sel_page_gp;
	    else
		gp = first_addgroup;
	    while (gp) {
		if ((!(gp->flags&AGF_SEL) ^ (ch=='J'))
		 || (gp->flags & AGF_DEL)) {
		    if (ch == 'J' || (gp->flags & AGF_INCLUDED))
			gp->flags |= AGF_EXCLUDED;
		    gp->flags &= ~(AGF_DEL|AGF_SEL);
		}
		gp = gp->next;
		if (ch == 'D' && gp == sel_next_gp)
		    break;
	    }
	    if (ch == 'J' || (ch == 'D' && !selected_count)) {
		init_pages(FILL_LAST_PAGE);
		if (sel_total_obj_cnt) {
		    sel_item_index = 0;
		    return DS_DISPLAY;
		}
	    }
	} else if (ch == 'J') {
	    register ADDGROUP* gp;
	    for (gp = first_addgroup; gp; gp = gp->next)
		gp->flags &= ~AGF_DELSEL;
	    selected_count = 0;
	    return DS_DISPLAY;
	}
	return DS_QUIT;
      case ':':
      case '/':
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (!finish_command(TRUE)) {	/* get rest of command */
	    if (clean_screen)
		break;
	}
	else {
	    if (ch == ':') {
		addgrp_sel_perform();
	    } else {
#ifdef NGSEARCH
		switch (ng_search(buf,FALSE)) {
		  case NGS_ERROR:
		  case NGS_ABORT:
		    break;
		  case NGS_INTR:
		    errormsg("Interrupted");
		    break;
		  case NGS_FOUND:
		  case NGS_NOTFOUND:
		  case NGS_DONE:
		    break;
		}
#else
		notincl("/");
#endif
	    }
	    sel_item_index = 0;

	    if (sel_perform_change(newsgroup_toread, "newsgroup"))
		return DS_UPDATE;
	    if (clean_screen)
		return DS_DISPLAY;
	}
	if (another_command(1))
	    return DS_DOCOMMAND;
	return DS_DISPLAY;
      case 'h':
      case '?':
	univ_help(UHELP_ADDSEL);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_addsel()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
      default:
	return DS_ERROR;
    }
    return DS_ASK;
}

static int
multirc_commands(ch)
char_int ch;
{
    switch (ch) {
      case 'R':
	set_selector(sel_mode, sel_sort * -sel_direction);
	sel_page_mp = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	sel_page_mp = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case '/':
	/*$$$$*/
	break;
      case 'h':
      case '?':
	univ_help(UHELP_MULTIRC);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_multirc()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
      default:
	return DS_ERROR;
    }
    return DS_ASK;
}

static int
option_commands(ch)
char_int ch;
{
    switch (ch) {
      case 'R':
	set_selector(sel_mode, sel_sort * -sel_direction);
	sel_page_op = 1;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	sel_page_op = 1;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'S':
	return DS_QUIT;
      case '/': {
	extern COMPEX optcompex;
	SEL_UNION u;
	char* s;
	char* pattern;
	int i, j;
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
	if (!finish_command(TRUE))	/* get rest of command */
	    break;
	s = cpytill(buf,buf+1,'/');
	for (pattern = buf; *pattern == ' '; pattern++) ;
	if ((s = compile(&optcompex,pattern,TRUE,TRUE)) != NULL) {
	    strcpy(msg,s);
	    return DS_STATUS;
	}
	i = j = sel_items[sel_item_index].u.op;
	do {
	    if (++i > obj_count)
		i = 1;
	    if (*options_ini[i].item == '*')
		continue;
	    if (execute(&optcompex,options_ini[i].item))
		break;
	} while (i != j);
	u.op = i;
	if (!(option_flags[i] & OF_INCLUDED)) {
	    for (j = i-1; *options_ini[j].item != '*'; j--) ;
	    option_flags[j] |= OF_SEL;
	    init_pages(FILL_LAST_PAGE);
	    calc_page(u);
	    return DS_DISPLAY;
	}
	if (calc_page(u))
	    return DS_DISPLAY;
	return DS_ASK;
      }
      case 'h':
      case '?':
	univ_help(UHELP_OPTIONS);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_options()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
      default:
	return DS_ERROR;
    }
    return DS_ASK;
}

static int
universal_commands(ch)
char_int ch;
{
    switch (ch) {
      case 'R':
	set_selector(sel_mode, sel_sort * -sel_direction);
	sel_page_univ = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
      case 'E':
	if (!sel_rereading)
	    sel_cleanup();
	sel_exclusive = !sel_exclusive;
	sel_page_univ = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
#ifdef SCAN_ART
      case ';':
	sel_ret = ';';
	return DS_QUIT;
#endif
      case 'U':
	sel_cleanup();
	sel_rereading = !sel_rereading;
	sel_page_univ = NULL;
	return DS_RESTART;
      case Ctl('e'):
	univ_edit();
	univ_redofile();
	sel_cleanup();
	sel_page_univ = NULL;
	return DS_RESTART;
      case 'h':
      case '?':
	univ_help(UHELP_UNIV);
	return DS_RESTART;
      case 'H':
	newline();
	if (another_command(help_univ()))
	    return DS_DOCOMMAND;
        return DS_DISPLAY;
#ifdef SCORE
      case 'O':
	if (!sel_rereading)
	    sel_cleanup();
	erase_line(mousebar_cnt > 0);	/* erase the prompt */
	removed_prompt = 3;
      reask_sort:
	in_char("Order by Natural, or score Points?", 'q', "npNP");
#ifdef VERIFY
	printcmd();
#endif
	if (*buf == 'h' || *buf == 'H') {
#ifdef VERBOSE
	    IF(verbose) {
		fputs("\n\
Type n or SP to order the items in the natural order.\n\
Type p to order the items by score points.\n\
",stdout) FLUSH;
		fputs("\
Typing your selection in upper case it will reverse the selected order.\n\
Type q to leave things as they are.\n\n\
",stdout) FLUSH;
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		fputs("\n\
n or SP sorts by natural order.\n\
p sorts by score.\n\
",stdout) FLUSH;
		fputs("\
Upper case reverses the sort.\n\
q does nothing.\n\n\
",stdout) FLUSH;
	    }
#endif
	    clean_screen = FALSE;
	    goto reask_sort;
	} else if (*buf == 'q' ||
		   (tolower(*buf) != 'n' && tolower(*buf) != 'p')) {
	    if (can_home)
		erase_line(0);
	    return DS_ASK;
	}
	set_sel_sort(sel_mode,*buf);
	sel_page_univ = NULL;
	init_pages(FILL_LAST_PAGE);
	return DS_DISPLAY;
#endif
      case '~':
	univ_virt_pass();
	sel_cleanup();
	sel_page_univ = NULL;
	return DS_RESTART;
      default:
	break;
    }
    return DS_ERROR;
}

static void
switch_dmode(dmode_cpp)
char** dmode_cpp;
{
    char* s;

    if (!*++*dmode_cpp) {
	do {
	    --*dmode_cpp;
	} while ((*dmode_cpp)[-1] != '*');
    }
    switch (**dmode_cpp) {
      case 's':
	s = "short";
	break;
      case 'm':
	s = "medium";
	break;
      case 'd':
	s = "date";
	break;
      case 'l':
	s = "long";
	break;
    }
    sprintf(msg,"(%s display style)",s);
    disp_status_line = 1;
}

static int
find_line(y)
int y;
{
    int i;
    for (i = 0; i < sel_page_item_cnt; i++) {
	if (sel_items[i].line > y)
	    break;
    }
    if (i > 0)
	i--;
    return i;
}

/* On click:
 *    btn = 0 (left), 1 (middle), or 2 (right) + 4 if double-clicked;
 *    x = 0 to COLS-1; y = 0 to LINES-1;
 *    btn_clk = 0, 1, or 2 (no 4); x_clk = x; y_clk = y.
 * On release:
 *    btn = 3; x = release's x; y = release's y;
 *    btn_clk = click's 0, 1, or 2; x_clk = click's x; y_clk = click's y.
 */
void
selector_mouse(btn, x,y, btn_clk, x_clk,y_clk)
int btn;
int x, y;
int btn_clk;
int x_clk, y_clk;
{
    if (check_mousebar(btn, x,y, btn_clk, x_clk,y_clk))
	return;

    if (btn != 3) {
	/* Handle button-down event */
	switch (btn_clk) {
	  case 0:
	  case 1:
	    if (y > 0 && y < sel_last_line) {
		if (btn & 4) {
		    pushchar(btn_clk == 0? '\n' : 'Z');
		    mouse_is_down = FALSE;
		}
		else {
		    force_sel_pos = find_line(y);
		    if (UseSelNum) {
			pushchar(('0' + (force_sel_pos+1) % 10) | 0200);
			pushchar(('0' + (force_sel_pos+1)/10) | 0200);
		    } else {
			pushchar(sel_chars[force_sel_pos] | 0200);
		    }
		    if (btn == 1)
			pushchar(Ctl('g') | 0200);
		}
	    }
	    break;
	  case 2:
	    break;
	}
    }
    else {
	/* Handle the button-up event */
	switch (btn_clk) {
	  case 0:
	    if (!y)
		pushchar('<' | 0200);
	    else if (y >= sel_last_line)
		pushchar(' ');
	    else {
		int i = find_line(y);
		if (sel_items[i].line != term_line) {
		    if (UseSelNum) {
			pushchar(('0' + (i+1) % 10) | 0200);
			pushchar(('0' + (i+1) / 10) | 0200);
		    } else {
			pushchar(sel_chars[i] | 0200);
		    }
		    pushchar('-' | 0200);
		    force_sel_pos = i;
		}
	    }
	    break;
	  case 1:
	    if (!y)
		pushchar('<' | 0200);
	    else if (y >= sel_last_line)
		pushchar('>' | 0200);
	    break;
	  case 2:
	    /* move forward or backwards a page:
	     *   if cursor in top half: backwards
	     *   if cursor in bottom half: forwards
	     */
	    if (y < LINES/2)
		pushchar('<' | 0200);
	    else
		pushchar('>' | 0200);
	    break;
	}
    }
}

/* Icky placement, but the PUSH/POP stuff is local to this file */
int
univ_visit_group(gname)
char* gname;
{
    PUSH_SELECTOR();

    univ_visit_group_main(gname);

    POP_SELECTOR();
    return 0;		/* later may have some error return values */
}

/* later consider returning universal_selector() value */
void
univ_visit_help(where)
int where;
{
    PUSH_SELECTOR();
    PUSH_UNIV_SELECTOR();

    univ_help_main(where);
    (void)universal_selector();

    POP_UNIV_SELECTOR();
    POP_SELECTOR();
}


syntax highlighted by Code2HTML, v. 0.9.1