/* $Id: ng.c,v 3.0 1992/02/01 03:09:32 davison Trn $
 */
/* This software is Copyright 1991 by Stan Barber. 
 *
 * Permission is hereby granted to copy, reproduce, redistribute or otherwise
 * use this software as long as: there is no monetary profit gained
 * specifically from the use or reproduction of this software, it is not
 * sold, rented, traded or otherwise marketed, and this copyright notice is
 * included prominently in any copy made. 
 *
 * The authors make no claims as to the fitness or correctness of this software
 * for any use whatsoever, and it is provided as is. Any use of this software
 * is at the user's own risk. 
 */

#include "EXTERN.h"
#include "common.h"
#include "trn.h"
#include "term.h"
#include "final.h"
#include "util.h"
#include "cache.h"
#include "bits.h"
#include "artsrch.h"
#include "help.h"
#include "kfile.h"
#include "rcstuff.h"
#include "head.h"
#include "art.h"
#include "artio.h"
#include "ngstuff.h"
#include "intrp.h"
#include "respond.h"
#include "ngdata.h"
#include "backpage.h"
#include "rcln.h"
#include "sw.h"
#include "last.h"
#include "search.h"
#include "nntp.h"
#include "rthread.h"
#include "rt-select.h"
#include "rt-wumpus.h"
#include "decode.h"
#include "charsubst.h"
#include "INTERN.h"
#include "ng.h"
#include "artstate.h"			/* somebody has to do it */

/* art_switch() return values */

#define AS_NORM 0
#define AS_INP 1
#define AS_ASK 2
#define AS_CLEAN 3

int exit_code = NG_NORM;

void
ng_init()
{

#ifdef KILLFILES
    open_kfile(KF_GLOBAL);
#endif
#ifdef CUSTOMLINES
    init_compex(&hide_compex);
    init_compex(&page_compex);
#endif
}

/* do newsgroup on line ng with name ngname */

/* assumes that we are chdir'ed to NEWSSPOOL, and assures that that is
 * still true upon return, but chdirs to NEWSSPOOL/ngname in between
 *
 * If you can understand this routine, you understand most of the program.
 * The basic structure is:
 *	for each desired article
 *		for each desired page
 *			for each line on page
 *				if we need another line from file
 *					get it
 *					if it's a header line
 *						do special things
 *				for each column on page
 *					put out a character
 *				end loop
 *			end loop
 *		end loop
 *	end loop
 *
 *	(Actually, the pager is in another routine.)
 *
 * The chief problem is deciding what is meant by "desired".  Most of
 * the messiness of this routine is due to the fact that people want
 * to do unstructured things all the time.  I have used a few judicious
 * goto's where I thought it improved readability.  The rest of the messiness
 * arises from trying to be both space and time efficient.  Have fun.
 */

int
do_newsgroup(start_command)
char *start_command;			/* command to fake up first */
{
    char oldmode = mode;
#ifdef CHARSUBST
    char *whatnext = "%s%sWhat next? [%s]";
    charsubst = charsets;
#else
    char *whatnext = "%sWhat next? [%s]";
#endif

    exit_code = NG_NORM;
    localkf_changes = 0;
    killfirst = 0;

    if (extractdest) {
	free(extractdest);
	extractdest = Nullch;
    }
    if (extractprog) {
	free(extractprog);
	extractprog = Nullch;
    }

    /* initialize the newsgroup data structures */

    if (!access_ng())
	return -1;

#ifdef ARTSEARCH
    srchahead = (scanon && !ThreadedGroup	/* did they say -S? */
	      && ((ART_NUM)toread[ng]) >= scanon ? -1 : 0);
#endif

    /* FROM HERE ON, RETURN THRU CLEANUP OR WE ARE SCREWED */

    forcelast = TRUE;			/* if 0 unread, do not bomb out */
    recent_artp = curr_artp = Nullart;
    recent_art = curr_art = lastart+1;
    prompt = whatnext;

    /* remember what newsgroup we were in for sake of posterity */

    writelast();

    /* see if there are any special searches to do */

    has_normal_kills = FALSE;
#ifdef KILLFILES
    open_kfile(KF_LOCAL);
# ifdef VERBOSE
    IF(verbose)
	kill_unwanted(firstart,"Processing memorized commands...\n\n",TRUE);
    ELSE
# endif
# ifdef TERSE
	kill_unwanted(firstart,"Auto-processing...\n\n",TRUE);
# endif
#endif
    if (!selected_count)
	selected_only = FALSE;
    top_article();

    /* do they want a special top line? */

    firstline = getval("FIRSTLINE",Nullch);

    /* custom line suppression, custom page ending */

#ifdef CUSTOMLINES
    if (hideline = getval("HIDELINE",Nullch))
	compile(&hide_compex,hideline,TRUE,TRUE);
    if (pagestop = getval("PAGESTOP",Nullch))
	compile(&page_compex,pagestop,TRUE,TRUE);
#endif

    /* now read each unread article */

    rc_changed = doing_ng = TRUE;	/* enter the twilight zone */
    if (!unsafe_rc_saves)
	checkcount = 0;			/* do not checkpoint for a while */
    do_fseek = FALSE;			/* start 1st article at top */
    for (; art<=lastart+1; ) {		/* for each article */
	mode = 'a';

	/* do we need to "grow" the newsgroup? */

	if ((art > lastart || forcegrow) && !keep_the_group_static) {
	    ART_NUM oldlast = lastart;
#ifdef USE_NNTP
	    if ((actfp || getngsize(ng) > lastart)
	     && !nntp_group(ngname,ng)) {
		fprintf(stderr,"Your server went south for the winter:\n%s\n",
			ser_line);
		finalize(1);
	    }
	    if (ngmax[ng] > lastart) {
		grow_ng(ngmax[ng]);
	    }
#else
	    grow_ng(getngsize(ng));
#endif
	    if (forcelast && art > oldlast)
		art = lastart+1;
	}
	if (!artp || (artp->flags & AF_TMPMEM) != AF_TMPMEM || art != 0)
	    artp = find_article(art);
	if (start_command) {		/* do we have an initial command? */
	    hide_pending();
	    pushstring(start_command, 0);
	    free(start_command);
	    start_command = Nullch;
	    art = curr_art = lastart+1;
	    artp = curr_artp = Nullart;
	    if (input_pending())
		goto reinp_article;
	}
	if (art>lastart) {		/* are we off the end still? */
	    ARTICLE *ap;
	    ART_NUM i;
	    art = lastart + 1;		/* keep pointer references sane */
	    if (!forcelast && toread[ng] && selected_only && !selected_count) {
		art = curr_art;
		artp = curr_artp;
		strcpy(buf, "+");
		goto article_level;
	    }
	    count_subjects(CS_RETAIN);
	    for (i = absfirst, ap = article_ptr(i); i <= lastart; i++, ap++)
		if (!(ap->flags & (AF_READ|AF_CACHED)))
		    article_count++;
	    toread[ng] = (ART_UNREAD)article_count;
	    if (artp != curr_artp) {
		recent_art = curr_art;	/* remember last article # (for '-') */
		curr_art = art;		/* set current article # */
		recent_artp = curr_artp;
		curr_artp = artp;
	    }
	    if (erase_screen)
		clear();			/* clear the screen */
	    else
		fputs("\n\n",stdout) FLUSH;
#ifdef VERBOSE
	    IF(verbose)
		printf("End of newsgroup %s.",ngname);
					/* print pseudo-article */
	    ELSE
#endif
#ifdef TERSE
		printf("End of %s",ngname);
#endif
	    if (article_count) {
		if (selected_only)
		    printf("  (%ld + %ld articles still unread)",
			(long)selected_count,
			(long)article_count-selected_count);
		else
		    printf("  (%ld article%s still unread)",
			(long)article_count,article_count==1?nullstr:"s");
	    }
	    if (redirected) {
		if (redirected == nullstr)
		    printf("\n\n** This group has been disabled by your news admin **");
		else
		    printf("\n\n** Please start using %s **", redirected);
	    } else if (!article_count && !forcelast)
		goto cleanup;		/* actually exit newsgroup */
	    mode = 'e';
	    prompt = whatnext;
#ifdef ARTSEARCH
	    srchahead = 0;		/* no more subject search mode */
#endif
	    fputs("\n\n",stdout) FLUSH;
	}
	else if (!reread && (was_read(art) || (artp->flags & AF_MISSING)
		|| (selected_only && !(artp->flags & AF_SEL)))) {
					/* has this article been read? */
	    inc_art(selected_only,FALSE);/* then skip it */
	    continue;
	}
	else if (!reread && !parseheader(art)) {
	    oneless(artp);		/* mark deleted as read */
	    ng_skip();
	}
	else {				/* we have a real live article */
	    if (artp != curr_artp) {
		recent_art = curr_art;	/* remember last article # (for '-') */
		curr_art = art;		/* set current article # */
		recent_artp = curr_artp;
		curr_artp = artp;
	    }
	    if (!do_fseek) {		/* starting at top of article? */
		artline = 0;		/* start at the beginning */
		topline = -1;		/* and remember top line of screen */
					/*  (line # within article file) */
	    }
	    clear();			/* clear screen */
#ifdef USE_NNTP
	    if (art == 0 && artp && artp->msgid
	     && !(artp->flags & AF_CACHED)) {
		art = nntp_stat_id(artp->msgid);
		if (art)
		    artp = find_article(art);
	    }
#endif
	    if (!artopen(art)) {	/* make sure article is found & open */
		char tmpbuf[256];
		ART_LINE linenum;
		/* see if we have tree data for this article anyway */
		init_tree();
		sprintf(tmpbuf,"%s: article is not available.",ngname);
		if (artp && !(artp->flags & AF_CACHED)) {
		    if (absfirst < first_cached || last_cached < lastart
		     || !cached_all_in_range)
			sprintf(tmpbuf,"%s: article may show up in a moment.",
				ngname);
		}
		linenum = tree_puts(tmpbuf,0,0);
		vwtary(artline,(ART_POS)0);
		finish_tree(linenum);
		prompt = whatnext;
#ifdef ARTSEARCH
		srchahead = 0;
#endif
	    }
	    else {			/* found it, so print it */
		switch (do_article()) {
		case DA_CLEAN:		/* quit newsgroup */
		    goto cleanup;
		case DA_TOEND:		/* do not mark as read */
		    goto reask_article; 
		case DA_RAISE:		/* reparse command at end of art */
		    goto article_level;
		case DA_NORM:		/* normal end of article */
		    break;
		}
	    }
	    if (art >= absfirst)	/* don't mark non-existant articles */
		mark_as_read();		/* mark current article as read */
	    do_hiding = TRUE;
#ifdef ROTATION
	    rotate = FALSE;
#endif
	}

/* if these gotos bother you, think of this as a little state machine */

reask_article:
#ifdef MAILCALL
	setmail(FALSE);
#endif
	setdfltcmd();
#ifdef CLEAREOL
	if (erase_screen && can_home_clear)
	    clear_rest();
#endif /* CLEAREOL */
	unflush_output();		/* disable any ^O in effect */
	standout();			/* enter standout mode */
	/* print prompt, whatever it is */
	interp(cmd_buf, sizeof cmd_buf, mailcall);
#ifdef CHARSUBST
	printf(prompt,cmd_buf,current_charsubst(),dfltcmd);
#else
	printf(prompt,cmd_buf,dfltcmd);
#endif
	un_standout();			/* leave standout mode */
	putchar(' ');
	fflush(stdout);
reinp_article:
	reread = FALSE;
	forcelast = FALSE;
	eat_typeahead();
#ifdef PENDING
	look_ahead();			/* see what we can do in advance */
	cache_until_key();
#endif
	art = curr_art;
	artp = curr_artp;
	getcmd(buf);
	if (errno || *buf == '\f') {
	    if (LINES < 100 && !int_count)
		*buf = '\f';		/* on CONT fake up refresh */
	    else {
		putchar('\n') FLUSH;		/* but only on a crt */
		goto reask_article;
	    }
	}
article_level:
	output_chase_phrase = TRUE;

	/* parse and process article level command */

	switch (art_switch()) {
	case AS_INP:			/* multichar command rubbed out */
	    goto reinp_article;
	case AS_ASK:			/* reprompt "End of article..." */
	    goto reask_article;
	case AS_CLEAN:			/* exit newsgroup */
	    goto cleanup;
	case AS_NORM:			/* display article art */
	    break;
	}
    }					/* end of article selection loop */
    
/* shut down newsgroup */

cleanup:
    decode_end();
#ifdef KILLFILES
    kill_unwanted(firstart,"\nCleaning up...\n\n",FALSE);
					/* do cleanup from KILL file, if any */
#endif
    chase_xrefs(FALSE);
    in_ng = FALSE;			/* leave newsgroup state */
    if (artfp != Nullfp) {		/* article still open? */
	fclose(artfp);			/* close it */
	artfp = Nullfp;			/* and tell the world */
	openart = 0;
    }
    putchar('\n') FLUSH;
    deselect_all();
    yankback();				/* do a Y command */
    bits_to_rc();			/* reconstitute .newsrc line */
    doing_ng = FALSE;			/* tell sig_catcher to cool it */
    if (!unsafe_rc_saves)
	write_rc();			/* and update .newsrc */
    rc_changed = FALSE;			/* tell sig_catcher it is ok */
    if (chdir(spool)) {
	printf(nocd,spool) FLUSH;
	sig_catcher(0);
    }
#ifdef KILLFILES
    if (localkfp) {
	fclose(localkfp);
	localkfp = Nullfp;
    }
#endif
    mode = oldmode;
    return exit_code;
}					/* Whew! */

/* decide what to do at the end of an article */

int
art_switch()
{
    register ART_NUM i;
      
    setdef(buf,dfltcmd);
#ifdef VERIFY
    printcmd();
#endif

    switch (*buf) {
    case '<':			/* goto previous subject/thread */
	visit_prev_thread();
	return AS_NORM;
    case '>':			/* goto next subject/thread */
	visit_next_thread();
	return AS_NORM;
    case 'U': {			/* unread some articles */
	char *u_prompt, *u_help_thread;

	if (!artp) {
	    u_help_thread = nullstr;
#ifdef VERBOSE
	    IF(verbose)
		u_prompt = "\nSet unread: +select or all?";
	    ELSE
#endif
#ifdef TERSE
		u_prompt = "\nSet unread?";
#endif
	    dfltcmd = "+an";
	}
	else {
#ifdef VERBOSE
	    IF(verbose) {
		u_prompt = "\n\
Set unread: +select, thread, subthread, or all?";
		u_help_thread = "\
Type t or SP to mark this thread's articles as unread.\n\
Type s to mark the current article and its descendants as unread.\n";
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		u_prompt = "\nSet unread?";
		u_help_thread = "\
t or SP to mark thread unread.\n\
s to mark subthread unread.\n";
	    }
#endif
	    dfltcmd = "+tsan";
	}
      reask_unread:
	in_char(u_prompt,'u',dfltcmd);
#ifdef VERIFY
	printcmd();
#endif
	putchar('\n') FLUSH;
	if (*buf == 'h') {
#ifdef VERBOSE
	    IF(verbose)
	    {
		fputs("\
Type + to enter select thread mode using all the already-read articles.\n\
(The selected threads will be marked as unread and displayed as usual.)\n\
",stdout) FLUSH;
		fputs(u_help_thread,stdout);
		fputs("\
Type a to mark all articles in this group as unread.\n\
Type n to change nothing.\n\
",stdout) FLUSH;
	    }
	    ELSE
#endif
#ifdef TERSE
	    {
		fputs("\
+ to select threads from the unread.\n\
",stdout) FLUSH;
		fputs(u_help_thread,stdout);
		fputs("\
a to mark all articles unread.\n\
n to change nothing.\n\
",stdout) FLUSH;
	    }
#endif
	    goto reask_unread;
	}
	else if (*buf == 'n' || *buf == 'q')
	    return AS_ASK;
	else if (*buf == 't' && u_help_thread != nullstr) {
	    unkill_thread(artp->subj->thread);
	    if ((artp = first_art(artp->subj)) != Nullart)
		art = article_num(artp);
	} else if (*buf == 's' && u_help_thread != nullstr)
	    unkill_subthread(artp);
	else if (*buf == 'a') {
	    register ARTICLE *ap;
	    check_first(absfirst);
	    ap = article_ptr(absfirst);
	    for (i = absfirst; i <= lastart; i++, ap++)
		if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ) {
		    ap->flags &= ~AF_READ;		/* mark as unread */
		    toread[ng]++;
		}
	    count_subjects(CS_NORM);
	}
	else if (*buf == '+') {
	    *buf = 'U';
	    goto run_the_selector;
	}
	else {
	    fputs(hforhelp,stdout) FLUSH;
	    settle_down();
	    goto reask_unread;
	}
	return AS_NORM;
    }
    case '[':			/* goto parent article */
    case '{':			/* goto thread's root article */
	if (artp && ThreadedGroup) {
	    if (!find_parent(*buf == '{')) {
		register char *cp = (*buf=='['?"parent":"root");
#ifdef VERBOSE
		IF(verbose)
		    printf("\nThere is no %s article prior to this one.\n",
			cp) FLUSH;
		ELSE
#endif
#ifdef TERSE
		    printf("\nNo prior %s.\n",cp) FLUSH;
#endif
		return AS_ASK;
	    }
	    reread = TRUE;
	    return AS_NORM;
	}
not_threaded:
	if (!artp) {
#ifdef VERBOSE
	    IF(verbose)
		fputs("\nYou're at the end of the group.\n",stdout) FLUSH;
	    ELSE
#endif
#ifdef TERSE
		fputs("\nEnd of group.\n",stdout) FLUSH;
#endif
	    return AS_ASK;
	}
#ifdef VERBOSE
	IF(verbose)
	    fputs("\nThis group is not threaded.\n",stdout) FLUSH;
	ELSE
#endif
#ifdef TERSE
	    fputs("\nUnthreaded group.\n",stdout) FLUSH;
#endif
	return AS_ASK;
    case ']':			/* goto child article */
    case '}':			/* goto thread's leaf article */
	if (artp && ThreadedGroup) {
	    if (!find_leaf(*buf == '}')) {
#ifdef VERBOSE
		IF(verbose)
		    fputs("\n\
This is the last leaf in this tree.\n",stdout) FLUSH;
		ELSE
#endif
#ifdef TERSE
		    fputs("\nLast leaf.\n",stdout) FLUSH;
#endif
		return AS_ASK;
	    }
	    reread = TRUE;
	    return AS_NORM;
	}
	goto not_threaded;
    case '(':			/* goto previous sibling */
    case ')':			/* goto next sibling */
	if (artp && ThreadedGroup) {
	    if (!(*buf == '(' ? find_prev_sib() : find_next_sib())) {
		register char *cp = (*buf == '(' ? "previous" : "next");
#ifdef VERBOSE
		IF(verbose)
		    printf("\nThis article has no %s sibling.\n",cp) FLUSH;
		ELSE
#endif
#ifdef TERSE
		    printf("\nNo %s sibling.\n",cp) FLUSH;
#endif
		return AS_ASK;
	    }
	    reread = TRUE;
	    return AS_NORM;
	}
	goto not_threaded;
    case 'T':
	if (!ThreadedGroup)
	    goto not_threaded;
	/* FALL THROUGH */
    case 'A':
	if (!artp)
	    goto not_threaded;
	switch (ask_memorize(*buf)) {
	case ',':  case 'j':
	    return AS_NORM;
	}
	return AS_ASK;
    case 'K':
	if (!artp)
	    goto not_threaded;
	/* first, write kill-subject command */
	(void)art_search(buf, (sizeof buf), TRUE);
	art = curr_art;
	artp = curr_artp;
	kill_subject(artp->subj,KF_ALL);/* take care of any prior subjects */
	return AS_NORM;
    case ',':		/* kill this node and all descendants */
	if (!artp)
	    goto not_threaded;
	if (ThreadedGroup)
	    kill_subthread(artp,KF_ALL);
	else if (art >= absfirst && art <= lastart)
	    mark_as_read();
	return AS_NORM;
    case 'J':		/* Junk all nodes in this thread */
	if (!artp)
	    goto not_threaded;
	if (ThreadedGroup) {
	    kill_thread(artp->subj->thread,KF_ALL);
	    return AS_NORM;
	}
	/* FALL THROUGH */
    case 'k':		/* kill current subject */
	if (!artp)
	    goto not_threaded;
	kill_subject(artp->subj,KF_ALL);
	if (!ThreadedGroup || last_cached < lastart) {
	    *buf = 'k';
	    goto normal_search;
	}
	return AS_NORM;
    case 't':
	carriage_return();
#ifndef CLEAREOL
	erase_eol();		/* erase the prompt */
#else
	if (erase_screen && can_home_clear)
	    clear_rest();
	else
	    erase_eol();	/* erase the prompt */
#endif /* CLEAREOL */
	fflush(stdout);
	page_line = 1;
	entire_tree(curr_artp);
	return AS_ASK;
    case ':':			/* execute command on selected articles */
	page_line = 1;
	if (!thread_perform())
	    return AS_INP;
	putchar('\n');
	art = curr_art;
	artp = curr_artp;
	return AS_ASK;
    case 'p':			/* find previous unread article */
	do {
	    dec_art(selected_only,FALSE);
	} while (art >= firstart && (was_read(art) || !parseheader(art)));
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	if (art >= firstart)
	    return AS_NORM;
	art = absfirst;	
	/* FALL THROUGH */
    case 'P':		/* goto previous article */
	dec_art(FALSE,TRUE);
      check_dec_art:
	if (art < absfirst) {
#ifdef VERBOSE
	    IF(verbose)
		printf("\nThere are no%s%s articles prior to this one.\n",
			*buf=='P'?nullstr:" unread",
			selected_only?" selected":nullstr) FLUSH;
	    ELSE
#endif
#ifdef TERSE
		printf("\nNo previous%s%s articles\n",
			*buf=='P'?nullstr:" unread",
			selected_only?" selected":nullstr) FLUSH;
#endif
	    art = curr_art;
	    artp = curr_artp;
	    return AS_ASK;
	}
	reread = TRUE;
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	return AS_NORM;
    case '-':
	if (recent_art >= 0) {
	    art = recent_art;
	    artp = recent_artp;
	    reread = TRUE;
	    forcelast = TRUE;
#ifdef ARTSEARCH
	    srchahead = -(srchahead != 0);
#endif
	    return AS_NORM;
	}
	else {
	    exit_code = NG_MINUS;
	    return AS_CLEAN;
	}
    case 'n':		/* find next unread article? */
	if (art > lastart) {
	    if (!toread[ng])
		return AS_CLEAN;
	    top_article();
	}
#ifdef ARTSEARCH
	else if (scanon && !ThreadedGroup && srchahead) {
	    *buf = Ctl('n');
	    if (!next_art_with_subj())
		goto normal_search;
	    return AS_NORM;
	}
#endif
	else {
	    inc_art(selected_only,FALSE);
	    if (art > lastart)
		top_article();
	}
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	return AS_NORM;
    case 'N':			/* goto next article */
	if (art > lastart)
	    if (!first_subject) {
		art = absfirst;
		artp = article_ptr(art);
	    } else {
		artp = first_subject->articles;
		if (artp->flags & AF_MISSING)
		    inc_art(FALSE,TRUE);
		else
		    art = article_num(artp);
	    }
	else
	    inc_art(FALSE,TRUE);
	if (art <= lastart)
	    reread = TRUE;
	else
	    forcelast = TRUE;
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	return AS_NORM;
    case '$':
	art = lastart+1;
	artp = Nullart;
	forcelast = TRUE;
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	return AS_NORM;
    case '1': case '2': case '3':	/* goto specified article */
    case '4': case '5': case '6':	/* or do something with a range */
    case '7': case '8': case '9': case '.':
	forcelast = TRUE;
	switch (numnum()) {
	case NN_INP:
	    return AS_INP;
	case NN_ASK:
	    return AS_ASK;
	case NN_REREAD:
	    reread = TRUE;
#ifdef ARTSEARCH
	    if (srchahead)
		srchahead = -1;
#endif
	    break;
	case NN_NORM:
	    if (was_read(art)) {
		top_article();
		pad(just_a_sec/3);
	    }
	    else {
		putchar('\n');
		return AS_ASK;
	    }
	    break;
	}
	return AS_NORM;
    case Ctl('k'):
	edit_kfile();
	return AS_ASK;
    case Ctl('n'):	/* search for next article with same subject */
    case Ctl('p'):	/* search for previous article with same subject */
	if (*buf == Ctl('n')? next_art_with_subj() : prev_art_with_subj())
	    return AS_NORM;
    case '/': case '?':
normal_search:
#ifdef ARTSEARCH
    {		/* search for article by pattern */
	char cmd = *buf;
	
	reread = TRUE;		/* assume this */
	page_line = 1;
	switch (art_search(buf, (sizeof buf), TRUE)) {
	case SRCH_ERROR:
	    art = curr_art;
	    return AS_ASK;
	case SRCH_ABORT:
	    art = curr_art;
	    return AS_INP;
	case SRCH_INTR:
#ifdef VERBOSE
	    IF(verbose)
		printf("\n(Interrupted at article %ld)\n",(long)art) FLUSH;
	    ELSE
#endif
#ifdef TERSE
		printf("\n(Intr at %ld)\n",(long)art) FLUSH;
#endif
	    art = curr_art;	    /* restore to current article */
	    return AS_ASK;
	case SRCH_DONE:
	    fputs("done\n",stdout) FLUSH;
	    pad(just_a_sec/3);	/* 1/3 second */
	    if (!srchahead) {
		art = curr_art;
		return AS_ASK;
	    }
	    top_article();
	    reread = FALSE;
	    return AS_NORM;
	case SRCH_SUBJDONE:
#ifdef UNDEF
	    fputs("\n\n\n\nSubject not found.\n",stdout) FLUSH;
	    pad(just_a_sec/3);	/* 1/3 second */
#endif
	    top_article();
	    reread = FALSE;
	    return AS_NORM;
	case SRCH_NOTFOUND:
	    fputs("\n\n\n\nNot found.\n",stdout) FLUSH;
	    art = curr_art;  /* restore to current article */
	    return AS_ASK;
	case SRCH_FOUND:
	    if (cmd == Ctl('n') || cmd == Ctl('p')) {
		oldsubject = TRUE;
		reread = FALSE;
	    }
	    break;
	}
	return AS_NORM;
    }
#else /* !ARTSEARCH */
	buf[1] = '\0';
	notincl(buf);
	return AS_ASK;
#endif
    case 'u':			/* unsubscribe from this newsgroup? */
	rcchar[ng] = NEGCHAR;
	return AS_CLEAN;
    case 'M':
	if (art <= lastart) {
	    delay_unmark(artp);
	    oneless(artp);
	    printf("\nArticle %ld will return.\n",(long)art) FLUSH;
	}
	return AS_ASK;
    case 'm':
	if (art >= absfirst && art <= lastart) {
	    unmark_as_read();
	    printf("\nArticle %ld marked as still unread.\n",(long)art) FLUSH;
	}
	return AS_ASK;
    case 'c':			/* catch up */
	switch (ask_catchup()) {
	case 'n':
	    return AS_ASK;
	case 'u':
	    return AS_CLEAN;
	}
	art = lastart+1;
	artp = Nullart;
	forcelast = FALSE;
	return AS_NORM;
    case 'Q':
	exit_code = NG_ASK;
	/* FALL THROUGH */
    case 'q':			/* go back up to newsgroup level? */
	return AS_CLEAN;
    case 'j':
	putchar('\n') FLUSH;
	if (art >= absfirst && art <= lastart)
	    mark_as_read();
	return AS_ASK;
    case 'h': {			/* help? */
	int cmd;

	if ((cmd = help_art()) > 0)
	    pushchar(cmd);
	return AS_ASK;
    }
    case '&':
	if (switcheroo()) /* get rest of command */
	    return AS_INP;	/* if rubbed out, try something else */
	return AS_ASK;
    case '#':
#ifdef VERBOSE
	IF(verbose)
	    printf("\nThe last article is %ld.\n",(long)lastart) FLUSH;
	ELSE
#endif
#ifdef TERSE
	    printf("\n%ld\n",(long)lastart) FLUSH;
#endif
	return AS_ASK;
    case '+':			/* enter selection mode */
run_the_selector:
	*buf = do_selector(*buf);
	switch (*buf) {
	case '+':
	    putchar('\n') FLUSH;
	    return AS_ASK;
	case 'Q':
	    exit_code = NG_ASK;
	    /* FALL THROUGH */
	case 'q':
	    break;
	case 'N':
	    exit_code = NG_SELNEXT;
	    break;
	case 'P':
	    exit_code = NG_SELPRIOR;
	    break;
	default:
	    if (toread[ng])
		return AS_NORM;
	    break;
	}
	return AS_CLEAN;
    case '=': {			/* list subjects */
	char tmpbuf[256];
	ART_NUM oldart = art;
	int cmd, len;
	char *s;
	char *subjline = getval("SUBJLINE",Nullch);
	ARTICLE *ap = article_ptr(firstart);

	page_init();
	for (i=firstart; i<=lastart && !int_count; i++, ap++) {
	    if (!(ap->flags & AF_READ) && (s = fetchsubj(i,FALSE)) != Nullch) {
		sprintf(tmpbuf,"%5ld ", i);
		len = strlen(tmpbuf);
		if (subjline) {
		    art = i;
		    interp(tmpbuf + len, sizeof tmpbuf - len, subjline);
		}
		else
		    safecpy(tmpbuf + len, s, sizeof tmpbuf - len);
		if (cmd = print_lines(tmpbuf,NOMARKING)) {
		    if (cmd > 0)
			pushchar(cmd);
		    break;
		}
	    }
	}
	int_count = 0;
	art = oldart;
	return AS_ASK;
    }
    case '^':
	top_article();
#ifdef ARTSEARCH
	srchahead = 0;
#endif
	return AS_NORM;
#ifdef DEBUG
    case 'D':
	printf("\nFirst article: %ld\n",(long)firstart) FLUSH;
	{
	    ARTICLE *ap = article_ptr(firstart);
	    for (i = firstart; i <= lastart && !int_count; i++, ap++) {
		if (ap->subj)
		    printf("%5ld %c %s\n",i,(was_read(i)?'y':'n'),
			   ap->subj->str) FLUSH;
	    }
	}
	int_count = 0;
	return AS_ASK;
#endif
    case 'v':
	if (art <= lastart) {
	    reread = TRUE;
	    do_hiding = FALSE;
	}
	return AS_NORM;
#ifdef ROTATION
    case Ctl('x'):
#endif
    case Ctl('r'):
#ifdef ROTATION
	rotate = (*buf==Ctl('x'));
#endif
	if (art <= lastart)
	    reread = TRUE;
	else
	    forcelast = TRUE;
	return AS_NORM;
#ifdef ROTATION
    case 'X':
	rotate = !rotate;
	/* FALL THROUGH */
#else
    case Ctl('x'):
    case 'x':
    case 'X':
	notincl("x");
	return AS_ASK;
#endif
    case 'l': case Ctl('l'):		/* refresh screen */
    refresh_screen:
	if (art <= lastart) {
	    reread = TRUE;
	    clear();
	    do_fseek = TRUE;
	    artline = topline;
	    if (artline < 0)
		artline = 0;
	}
	return AS_NORM;
    case Ctl('^'):
	carriage_return();
	erase_eol();		/* erase the prompt */
#ifdef MAILCALL
	setmail(TRUE);		/* force a mail check */
#endif
	return AS_ASK;
#ifdef INNERSEARCH
    case Ctl('e'):
	if (art <= lastart) {
	    reread = TRUE;
	    do_fseek = TRUE;
	    innerlight = artline - 1;
	    topline = artline;
	    innersearch = artsize;
	    gline = 0;
	    hide_everything = 'b';
	}
	return AS_NORM;
#endif
    case 'B':				/* back up one line */
    case 'b': case Ctl('b'):		/* back up a page */
	if (art <= lastart) {
	    ART_LINE target;

	    reread = TRUE;
	    clear();
	    do_fseek = TRUE;
	    if (*buf == 'B')
		target = topline - 1;
	    else {
		target = topline - (LINES - 2);
		if (marking && (marking_areas & BACKPAGE_MARKING)) {
		    highlight = topline;
		}
	    }
	    artline = topline;
	    if (artline >= 0) do {
		artline--;
	    } while(artline >= 0 && artline > target && vrdary(artline-1) >= 0);
	    topline = artline;
	    if (artline < 0)
		artline = 0;
	}
	return AS_NORM;
    case '!':			/* shell escape */
	if (escapade())
	    return AS_INP;
	return AS_ASK;
    case 'C': {
	cancel_article();
	return AS_ASK;
    }
    case 'Z':
    case 'z': {
	supersede_article();	/* supersedes */
	return AS_ASK;
    }
    case 'R':
    case 'r': {			/* reply? */
	reply();
	return AS_ASK;
    }
    case 'F':
    case 'f': {			/* followup command */
	followup();
	forcegrow = TRUE;		/* recalculate lastart */
	return AS_ASK;
    }
    case Ctl('f'): {			/* forward? */
	forward();
	return AS_ASK;
    }
    case '|':
    case 'w': case 'W':
    case 's': case 'S':		/* save command */
    case 'e':			/* extract command */
	if (save_article() == SAVE_ABORT)
	    return AS_INP;
	int_count = 0;
	return AS_ASK;
    case 'E':
	if (decode_fp)
	    decode_end();
	else
	    putchar('\n') FLUSH;
	return AS_ASK;
    case 'Y':				/* yank back M articles */
	yankback();
	top_article();			/* from the beginning */
	return AS_NORM;			/* pretend nothing happened */
#ifdef STRICTCR
    case '\n':
	fputs(badcr,stdout) FLUSH;
	return AS_ASK;
#endif
    case '_':
	if (!finish_dblchar())
	    return AS_INP;
	switch (buf[1] & 0177) {
	case 'P':
	    art--;
	    goto check_dec_art;
	case 'N':
	    if (art > lastart)
		art = absfirst;
	    else
		art++;
	    if (art <= lastart)
		reread = TRUE;
#ifdef ARTSEARCH
	    srchahead = 0;
#endif
	    return AS_NORM;
	case '+':
	    if (!artp)
		goto not_threaded;
	    if (ThreadedGroup) {
		select_arts_thread(artp, 0);
		printf("\nSelected all articles in this thread.\n");
	    } else {
		select_arts_subject(artp, 0);
		printf("\nSelected all articles in this subject.\n");
	    }
	    if ((artp = first_art(artp->subj)) != Nullart) {
		if (art == article_num(artp))
		    return AS_ASK;
		art = article_num(artp);
	    }
	    return AS_NORM;
	case '-':
	    if (!artp)
		goto not_threaded;
	    if (sel_mode == SM_THREAD) {
		deselect_arts_thread(artp);
		printf("\nDeselected all articles in this thread.\n");
	    } else {
		deselect_arts_subject(artp);
		printf("\nDeselected all articles in this subject.\n");
	    }
	    return AS_ASK;
#ifdef CHARSUBST
	case 'C':
	    if (!*(++charsubst))
		charsubst = charsets;
	    goto refresh_screen;
#endif
	case 'a':  case 's':  case 't':  case 'T':
	    *buf = buf[1];
	    goto run_the_selector;
	}
	/* FALL THROUGH */
    default:
	printf("\n%s",hforhelp) FLUSH;
	settle_down();
	break;
    }
    return AS_ASK;
}

#ifdef MAILCALL
/* see if there is any mail */

void
setmail(force)
bool_int force;
{
    if (force)
	mailcount = 0;
    if (!(mailcount++)) {
	char *mailfile = filexp(getval("MAILFILE",MAILFILE));
	
	if (stat(mailfile,&filestat) < 0 || !filestat.st_size
	    || filestat.st_atime > filestat.st_mtime)
	    mailcall = nullstr;
	else
	    mailcall = getval("MAILCALL","(Mail) ");
    }
    mailcount %= 5;			/* check every 5 articles */
}
#endif

void
setdfltcmd()
{
    if (!toread[ng]) {
	if (art > lastart)
	    dfltcmd = "qnp";
	else
	    dfltcmd = "npq";
    }
    else {
#ifdef ARTSEARCH
	if (srchahead)
	    dfltcmd = "^Nnpq";
	else
#endif
	    dfltcmd = "npq";
    }
}

/* Ask the user about catching-up the current group.  Returns 'y' if yes,
** 'n' or 'N' if no ('N' means we used one line when in the selector),
** or 'u' for yes with unsubscribe.  Actually performs the catchup and
** unsubscription as needed.
*/
char
ask_catchup()
{
    char ch;
    bool use_one_line = (mode == 't');
    int leave = 0;

    if (!use_one_line)
	putchar('\n') FLUSH;
reask_catchup:
#ifdef VERBOSE
    IF(verbose)
	in_char("Do you really want to mark everything as read?",'C',"yn#h");
    ELSE
#endif
#ifdef TERSE
	in_char("Really?",'C',"ynh");
#endif
#ifdef VERIFY
    printcmd();
#endif
    if ((ch = *buf) == 'h') {
	use_one_line = FALSE;
#ifdef VERBOSE
	IF(verbose)
	    fputs("\n\
Type y or SP to mark all articles as read.\n\
Type n to leave articles marked as they are.\n\
Enter a number to mark all but the last # articles as read.\n\
Type u to mark everything read and unsubscribe.\n\n\
",stdout) FLUSH;
	ELSE
#endif
#ifdef TERSE
	    fputs("\n\
y or SP to mark all read.\n\
n to forget it.\n\
u to mark all and unsubscribe.\n\n\
",stdout) FLUSH;
#endif
	goto reask_catchup;
    }
    if (ch == 'n' || ch == 'q') {
	if (use_one_line)
	    return 'N';
	putchar('\n') FLUSH;
	return 'n';
    }
    if (ch == '#') {
	use_one_line = FALSE;
	in_char("\nEnter the number of articles to leave unread: ", 'C', "0");
	if ((ch = *buf) == '0')
	    ch = 'y';
    }
    if (isdigit(ch)) {
	buf[1] = FINISHCMD;
	if (!finish_command(FALSE)) {
	    use_one_line = FALSE;
	    putchar('\n') FLUSH;
	    goto reask_catchup;
	}
	else {
	    leave = atoi(buf);
	    ch = 'y';
	}
    }
    if (ch != 'y' && ch != 'u') {
	use_one_line = FALSE;
	printf("\n%s\n", hforhelp) FLUSH;
	settle_down();
	goto reask_catchup;
    }
    if (mode == 'n') {
	putchar('\n') FLUSH;
	catch_up(ng, leave);
    }
    else {
	int i;
	ARTICLE *ap;
	for (i = firstart, ap = article_ptr(i); i <= lastart-leave; i++, ap++)
	    ap->flags = ((ap->flags & ~sel_mask) | AF_READ);
	selected_count = selected_subj_cnt = selected_only = 0;
	toread[ng] = 0;
	if (dmcount)
	    yankback();
	putchar('\n') FLUSH;
    }
    if (ch == 'u') {
	rcchar[ng] = NEGCHAR;
	printf("(If you meant to hit 'y' instead of 'u', press '-'.)\n");
    }
    return ch;
}

char
ask_memorize(ch)
char_int ch;
{
    bool thread_cmd = (ch == 'T');
    bool use_one_line = (mode == 't');
    char *mode_string = (thread_cmd? "thread" : "subject");
    char *mode_phrase = (thread_cmd? "replies to this article" :
				     "this subject and all replies");
    ART_NUM art_hold = art;
    ARTICLE *artp_hold = artp;

    if (!use_one_line)
	putchar('\n') FLUSH;
    sprintf(cmd_buf,"Memorize %s command:", mode_string);
reask_memorize:
    in_char(cmd_buf, 'm', "+.j,cC");
#ifdef VERIFY
    printcmd();
#endif
    if ((ch = *buf) == 'h') {
	use_one_line = FALSE;
#ifdef VERBOSE
	IF(verbose)
	    printf("\n\
Type + or SP to auto-select this %s (i.e. includes future articles).\n\
Type . to auto-select %s.\n\
Type j to auto-kill (junk) this %s.\n\
Type , to auto-kill %s.\n\
Type c to clear all selection/killing on this %s.\n\
Type C to clear all selection/killing on %s.\n\
Type q to abort the operation.\n\n\
",mode_string,mode_phrase,mode_string,mode_phrase,mode_string,mode_phrase) FLUSH;
	ELSE
#endif
#ifdef TERSE
	    printf("\n\
+ or SP auto-selects this %s.\n\
. auto-selects %s.\n\
j auto-kills this %s.\n\
, auto-kills %s.\n\
c clears auto-commands for this %s.\n\
C clears auto-commands for %s.\n\
q aborts.\n\n\
",mode_string,mode_phrase,mode_string,mode_phrase,mode_string,mode_phrase) FLUSH;
#endif
	goto reask_memorize;
    }
    if (ch == 'q') {
	if (use_one_line)
	    return 'Q';
	putchar('\n');
	return 'q';
    }
    if (ch == '+') {
	if (!thread_cmd) {
	    (void)art_search(buf, (sizeof buf), TRUE);
	    art = art_hold;
	    artp = artp_hold;
	    ch = '.';
	} else
	    ch = (use_one_line? '+' : '.');
	if (thread_cmd)
	    select_arts_thread(artp, AUTO_SELECTALL);
	else
	    select_arts_subject(artp, 0);
	if (mode != 't')
	    printf("\nSelection memorized.\n");
    } else if (ch == '.') {
	if (!thread_cmd) {
	    (void)art_search(buf, (sizeof buf), TRUE);
	    art = art_hold;
	    artp = artp_hold;
	} else
	    ch = (use_one_line? '+' : '.');
	select_subthread(artp,thread_cmd? AUTO_SELECT : 0);
	if (mode != 't')
	    printf("\nSelection memorized.\n");
    } else if (ch == 'j') {
	if (!thread_cmd) {
	    *buf = 'K';
	    (void)art_search(buf, (sizeof buf), TRUE);
	    art = art_hold;
	    artp = artp_hold;
	}
	if (thread_cmd)
	    kill_thread(artp->subj->thread,KF_ALL|KF_KILLFILE);
	else
	    kill_subject(artp->subj,KF_ALL);
	if (mode != 't')
	    printf("\nKill memorized.\n");
    } else if (ch == ',') {
	if (!thread_cmd) {
	    (void)art_search(buf, (sizeof buf), TRUE);
	    art = art_hold;
	    artp = artp_hold;
	}
	kill_subthread(artp,KF_ALL|(thread_cmd?KF_KILLFILE:0));
	if (mode != 't')
	    printf("\nKill memorized.\n");
    } else if (ch == 'c') {
	if (thread_cmd)
	    clear_thread(artp->subj->thread);
	else
	    clear_subject(artp->subj);
    } else if (ch == 'C') {
	clear_subthread(artp);
    } else {
	use_one_line = FALSE;
	printf("\n%s\n", hforhelp) FLUSH;
	settle_down();
	goto reask_memorize;
    }
    if (!use_one_line)
	putchar('\n') FLUSH;
    return ch;
}
