ftp.nice.ch/pub/next/unix/network/news/slrn0.9.0.0.s.tar.gz#/slrn/src/art.c

This is art.c in view mode; [Download] [Up]

/* -*- mode: C; mode: fold; -*- */
/*  Copyright (c) 1996 John E. Davis (davis@space.mit.edu)
 *  All rights reserved.
 */
#include "config.h"
#include "features.h"

/*{{{ system include files */
#include <stdio.h>
#include <string.h>
#include <time.h>


#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#include <ctype.h>
#include <slang.h>
#include "jdmacros.h"


#ifndef isalpha
# define isalpha(x) \
  ((((x) <= 'Z') && ((x) >= 'A')) \
   || (((x) <= 'z') && ((x) >= 'a')))
#endif

#ifndef isspace
# define isspace(x) (((x) == ' ') || ((x) == '\t'))
#endif

/*}}}*/
/*{{{ slrn include files */
#include "slrn.h"
#include "group.h"
#include "server.h"
#include "art.h"
#include "misc.h"
#include "post.h"
/* #include "clientlib.h" */
#include "startup.h"
#include "hash.h" 
#include "score.h"
#include "uudecode.h"
#include "menu.h"

#if SLRN_HAS_MIME 
# include "mime.h"
#endif

#if SLRN_HAS_GROUPLENS
# include "grplens.h"
#endif

/*}}}*/

/*{{{ extern Global variables  */

SLKeyMap_List_Type *Slrn_Article_Keymap;
char *Slrn_X_Browser;
char *Slrn_NonX_Browser;
char *Slrn_Quote_String = ">";
char *Slrn_Save_Directory;
int Slrn_Startup_With_Article = 0;
int Slrn_Show_Author = 2;		       /* If non-zero, show author in header */
int Slrn_Query_Next_Article = 1;
int Slrn_Query_Next_Group = 1;
int Slrn_Auto_CC_To_Poster = 0;
int Slrn_Score_After_XOver;
int Slrn_Use_Tmpdir = 0;
int Slrn_Threads_Visible = 0;
int Slrn_Use_Header_Numbers = 1;
#if SLRN_HAS_SORT_BY_SCORE
int Slrn_Display_Score;
#endif
int Slrn_Reads_Per_Update = 50;
int Slrn_Sig_Is_End_Of_Article = 0;
#if SLRN_HAS_SPOILERS
int Slrn_Spoiler_Char = 42;
#endif

char *Slrn_Current_Group_Name;
Slrn_Header_Type *Slrn_First_Header;
Slrn_Header_Type *Slrn_Current_Header;

/* range of articles on server for current group */
int Slrn_Server_Min, Slrn_Server_Max;

/* Sorting mode:
 *   0 No sorting
 *   1 sort by threads
 *   2 sort by subject
 *   3 sort by subject and threads
 *   4 sort by score
 *   5 sort by score and threads
 *   6 sort by score then by subject
 *   7 thread, then sort by score then subject
 */
#define SORT_BY_THREADS  1
#define SORT_BY_SUBJECT  2
#ifdef SLRN_HAS_SORT_BY_SCORE
# define SORT_BY_SCORE   4
#endif
int Slrn_Sorting_Mode = (SORT_BY_THREADS|SORT_BY_SUBJECT);

#define ALL_THREAD_FLAGS (FAKE_PARENT | FAKE_CHILDREN | FAKE_HEADER_HIGH_SCORE)

Slrn_Article_Line_Type *Slrn_Article_Lines;

/*}}}*/
/*{{{ static global variables */

static int Header_Number;	       /* line number of header window */
static int Num_Headers;
static unsigned int Number_Killed;
static unsigned int Number_High_Scored;
static unsigned int Number_Low_Scored;
static int User_Aborted_Group_Read;
#if SLRN_HAS_SPOILERS
static Slrn_Header_Type *Spoilers_Visible;
#endif

#if SLRN_HAS_GROUPLENS
static int Num_GroupLens_Rated = -1;
#endif
static Slrn_Header_Type *Mark_Header;  /* header with mark set */

static Slrn_Group_Type *Current_Group; /* group being processed */

static Slrn_Header_Type *Headers;
static int Last_Cursor_Row;	       /* row where --> cursor last was */
static Slrn_Header_Type *Top_Header;	       /* header at top of window */
static Slrn_Header_Type *Header_Showing;    /* header whose article is selected */
static Slrn_Header_Type *Last_Read_Header;
static int Article_Visible;	       /* non-zero if article window is visible */
static int Article_Window_Height;
static int Header_Window_Height;
static int Article_Line_Number;		       /* line number of article */
static int Article_Total_Lines;		       /* number of article lines */
static char Output_Filename[256];
static Slrn_Mode_Type *Last_Current_Mode;
static int Headers_Threaded;

/* If +1, threads are all collapsed.  If zero, none are.  If -1, some may
 * be and some may not.  In other words, if -1, this variable should not
 * be trusted.
 */
static int Threads_Collapsed = 0;


#define HEADER_TABLE_SIZE 1250
static Slrn_Header_Type *Header_Table[HEADER_TABLE_SIZE];
static int Headers_Hidden_Mode = 1;
static int Quotes_Hidden = 0;
static char *Super_Cite_Regexp = "^[^A-Za-z0-9]*\"\\([a-zA-Z/]+\\)\" == .+";
static int Group_Modified;
static int Do_Rot13;
static int Perform_Scoring;
static int Largest_Header_Number;

static int HScroll;
static SLKeymap_Function_Type *Art_Functions_Ptr;
static Slrn_Article_Line_Type *Top_Line, *Current_Line;


static Slrn_Header_Type *At_End_Of_Article;
/* If this variable is NULL, then we are not at the end of an article.  If it
 * points at the current article, the we are at the end of that article.
 * If it points anywhere else, ignore it.
 */

/*}}}*/
/*{{{ static function declarations */

static void slrn_art_hangup (int);
static void sort_threads (void);
static void hide_quotes (void);
static void wrap_article (void);
static void uncollapse_this_thread (Slrn_Header_Type *);
/* This is the function that is called to update the screen for this mode. */
static void art_update_screen (void);
static void art_next_unread (void);
static void (*Slrn_Group_Hangup_Hook) (int);
static void thread_headers (void);
static void sort_by_threads (void);
static void sort_by_sorting_mode (void);
static int select_article (int);
static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *);
static void quick_help (void);
static void for_this_tree (Slrn_Header_Type *, void (*)(Slrn_Header_Type *));

static void skip_to_next_group (void);

#if SLRN_HAS_SPOILERS
static void show_spoilers (void);
#endif
/*}}}*/

/*{{{ utility functions */

static int is_blank_line (unsigned char *b)
{
   b = (unsigned char *) slrn_skip_whitespace ((char *) b);
   return (*b == 0);
}

static void digit_arg (void)
{
   slrn_digit_arg (Slrn_Article_Keymap);
}

/*}}}*/
/*{{{ SIGWINCH and window resizing functions */
static void art_winch (void)
{
   static int rows;
   
   if ((rows != SLtt_Screen_Rows) 
       || (Article_Window_Height == 0) 
       || (Article_Window_Height >= SLtt_Screen_Rows - 4))
     {
	rows = SLtt_Screen_Rows;
	
	Article_Window_Height = (3 * rows) / 4;
	if (rows <= 28)
	  Article_Window_Height = rows - 8;
     }
   
   if (Article_Visible)
     {
	Header_Window_Height = rows - Article_Window_Height - 4;
     }
   else
     {
	Header_Window_Height = rows - 3;
     }
   
   if (Header_Window_Height < 1) Header_Window_Height = 1;
   if (Article_Window_Height < 1) Article_Window_Height = 1;
   Slrn_Full_Screen_Update = 1;
}

static void art_winch_sig (void)
{
   Article_Window_Height = 0;
   art_winch ();
}

static void shrink_window (void)
{
   if (Article_Visible == 0) return;
   Article_Window_Height++;
   art_winch ();
}

static void enlarge_window (void)
{
   if (Article_Visible == 0) return;
   Article_Window_Height--;
   art_winch ();
}

/*}}}*/
/*{{{ header hash functions */
static void delete_hash_table (void)
{
   SLMEMSET ((char *) Header_Table, 0, sizeof (Header_Table));
}

static void make_hash_table (void)
{
   Slrn_Header_Type *h;
   delete_hash_table ();
   h = Slrn_First_Header;
   while (h != NULL)
     {
	h->hash_next = Header_Table[h->hash % HEADER_TABLE_SIZE];
	Header_Table[h->hash % HEADER_TABLE_SIZE] = h;
	h = h->real_next;
     }
}

/*}}}*/

/*{{{ article line specific functions */
static void free_article (void)
{
   Slrn_Article_Line_Type *l, *next;
   
   l = Slrn_Article_Lines;
   while (l != NULL)
     {
	SLFREE (l->buf);
	next = l->next;
	SLFREE (l);
	l = next;
     }
   Top_Line = Current_Line = Slrn_Article_Lines = NULL;
   Header_Showing = NULL;
   Header_Window_Height = SLtt_Screen_Rows - 3;
   Article_Visible = 0;
}

static void find_article_line_number (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   int n = 0;
   while (l != NULL)
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     n++;
	     if (l == Current_Line) break;
	  }
	
	l = l->next;
     }
   
   if (l == NULL)
     {
	l = Slrn_Article_Lines;
	n = 0;
	while (l != NULL)
	  {
	     if ((l->flags & HIDDEN_LINE) == 0)
	       {
		  Current_Line = l;
		  n = 1;
		  break;
	       }
	     l = l->next;
	  }
     }
   Article_Line_Number = n;
}

static void count_article_lines (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   int n = 0;
   while (l != NULL)
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     n++;
	  }
	l = l->next;
     }
   Article_Total_Lines = n;
}

static void hide_art_headers (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   char ch, ch1, ch2;
   
   while ((l != NULL) && ((ch = *l->buf) != 0))
     {
	int hide_header;
	
	ch |= 0x20;
	ch1 = l->buf[1];
	if (ch1) ch2 = l->buf[2]; else ch2 = 0;
	ch1 |= 0x20;
	ch2 |= 0x20;
	l->flags = HEADER_LINE;
	
	hide_header = 0;
	switch (ch)
	  {
	   case 'r':		       /* references, etc... */
	     if (slrn_case_strncmp ((unsigned char *)l->buf, 
				     (unsigned char *) "Reply-To:", 9))
	       hide_header = 1;
	     break;
	   case 'n':		       /* nntp */
	     if (ch1 == 'e') break;
	     /* drop */
	   case 'i':		       /* In-Reply-To */
	   case 'x':		       /* user defined */
	   case 'a':		       /* approved */
	   case 'p':		       /* path */
	   case 'l':		       /* lines */
	   case 'm':		       /* msgid, mime */
	   case 'c':		       /* content */
	   case 't':		       /* To */
	     hide_header = 1;
	     break;
	     
	   case 'd':		       /* Distribution */
	     if (ch1 == 'a') break;    /* Date ok */
	     hide_header = 1;
	     break;
	     
	   case 's':		       /* sender etc... */
	     if ((ch1 == 'u') && (ch2 == 'b')) break;
	     hide_header = 1;
	  }
	
	do
	  {
	     l->flags |= HEADER_LINE;
	     if (Headers_Hidden_Mode && hide_header)
	       {
		  l->flags |= HIDDEN_LINE;
	       }
	     else l->flags &= ~HIDDEN_LINE;
	     
	     l = l->next;
	  }
	while ((l != NULL) && ((*l->buf == ' ') || (*l->buf == '\t')));
     }
   Slrn_Full_Screen_Update = 1;
   find_article_line_number ();
   count_article_lines ();
}



static void skip_quoted_text (void)
{
   Slrn_Article_Line_Type *l = Current_Line;
   int linenum = -1;
   
   /* look for a quoted line */
   while (l != NULL)
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     Current_Line = l;
	     linenum++;
	     if (l->flags & QUOTE_LINE) break;
	  }
	l = l->next;
     }
   if (linenum != -1) Article_Line_Number += linenum;
   
   /* Now we are either at the end of the buffer or on a quote line. Skip
    * past other quote lines.
    */
   
   if (l == NULL) return;
   l = l->next;
   
   while (l != NULL)
     {
	if (l->flags & HIDDEN_LINE)
	  {
	     l = l->next;
	     continue;
	  }
	Article_Line_Number++;
	Current_Line = l;
	if ((l->flags & QUOTE_LINE) == 0)
	  {
	     /* Check to see if it is blank */
	     if (is_blank_line ((unsigned char *) l->buf) == 0) break;
	  }
	l = l->next;
     }
}

static void skip_digest_forward (void)
{
   Slrn_Article_Line_Type *l;
   int num_passes;
   
   /* We are looking for:
    * <blank line>  (actually, most digests do not have this-- even the FAQ that suggests it!!)
    * ------------------------------
    * <blank line>
    * Subject: something
    *
    * In fact, most digests do not conform to this.  So, I will look for:
    * <blank line>
    * Subject: something
    *
    * Actually, most faqs, etc... do not support this.  So, look for any line
    * beginning with a number on second pass.  Sigh.
    */
   num_passes = 0;
   while (num_passes < 2)
     {
	l = Current_Line;
	if (l != NULL) l = l->next;
	
	while (l != NULL)
	  {
	     char ch;
	     char *buf;
	     
	     if ((l->flags & HIDDEN_LINE) || (l->flags & HEADER_LINE))
	       {
		  l = l->next;
		  continue;
	       }
	     
	     buf = l->buf;
	     if (num_passes == 0)
	       {
		  if ((strncmp ("Subject:", buf, 8))
		      || (((ch = buf[8]) != ' ') && (ch != '\t')))
		    {
		       l = l->next;
		       continue;
		    }
	       }
	     else
	       {
		  ch = *buf;
		  if ((ch > '9') || (ch < '0'))
		    {
		       l = l->next;
		       continue;
		    }
	       }
	     
	     Current_Line = l;
	     find_article_line_number ();
	     return;
	  }
	num_passes++;
     }
   slrn_error ("No next digest.");
}


static int try_supercite (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last, *lsave;
   static unsigned char compiled_pattern_buf[256];
   static SLRegexp_Type re;
   unsigned char *b;
   int count;
   char name[32];
   unsigned int len;
   int ret;
   
   re.pat = (unsigned char *) Super_Cite_Regexp;
   re.buf = compiled_pattern_buf;
   re.case_sensitive = 1;
   re.buf_len = sizeof (compiled_pattern_buf);
   
   /* skip header --- I should look for Xnewsreader: gnus */
   while ((l != NULL) && (*l->buf != 0)) l = l->next;
   
   if ((*compiled_pattern_buf == 0) && SLang_regexp_compile (&re))
     return -1;
   
   /* look at the first 15 lines on first attempt.
    * After that, scan the whole buffer looking for more citations */
   count = 15;
   lsave = l;
   ret = -1;
   while (1)
     {
	while (count && (l != NULL))
	  {
	     if ((l->flags & QUOTE_LINE) == 0)
	       {
		  if (NULL != slrn_regexp_match (&re, l->buf))
		    {
		       l->flags |= QUOTE_LINE;
		       break;
		    }
	       }
	     l = l->next;
	     count--;
	  }
	
	if ((l == NULL) || (count == 0)) return ret;
	
	/* Now find out what is used for citing. */
	b = (unsigned char *) l->buf + re.beg_matches[1];
	len = re.end_matches[1];
	if (len > sizeof (name) - 2) return ret;
	
	ret = 0;
	strncpy (name, (char *) b, len); name[len] = 0;
	/* strcat (name, ">");
	 len++; */
	
	while (l != NULL)
	  {
	     unsigned char ch;
	     
	     b = (unsigned char *) l->buf;
	     last = l;
	     l = l->next;
	     if (last->flags & QUOTE_LINE) continue;
	     
	     b = (unsigned char *) slrn_skip_whitespace ((char *) b);

	     if (!strncmp ((char *) b, name, len)
		 && (((ch = b[len] | 0x20) < 'a')
		     || (ch > 'z')))
	       {
		  last->flags |= QUOTE_LINE;
		  
		  while (l != NULL)
		    {
		       b = (unsigned char *) slrn_skip_whitespace (l->buf);
		       if (strncmp ((char *) b, name, len)
			   || (((ch = b[len] | 0x20) >= 'a')
			       && (ch <= 'z')))
			 break;
		       l->flags |= QUOTE_LINE;
		       l = l->next;
		    }
	       }
	  }
	count = -1;
	l = lsave;
     }
}


#if SLRN_HAS_SPOILERS
static void mark_spoilers (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   int spoiler = 0;
   
   /* skip header */
   while ((l != NULL) && (l->flags & HEADER_LINE))
     l = l->next;
   
   while (l != NULL)
     {
	if ((l->buf[0] == 12) && (l->buf[1] == 0))
	  {
	     spoiler = 1;
	  }
	else if (spoiler)
	  {
	     l->flags |= SPOILER_LINE;
	  }
	l = l->next;
     }   
}
#endif

static void mark_quotes (void)
{
   Slrn_Article_Line_Type *l, *last;
   unsigned char *b;
   SLRegexp_Type **r;
   
   if (0 == try_supercite ())
     {
	/* return; */
     }
   
   if (Slrn_Ignore_Quote_Regexp[0] == NULL) return;
   
   /* skip header */
   l = Slrn_Article_Lines;
   while ((l != NULL) && (*l->buf != 0)) l = l->next;
   
   while (l != NULL)
     {
	unsigned int min_len;
	b = (unsigned char *) l->buf;
	min_len = strlen ((char *) b);
	last = l;
	
	l = l->next;		       /* if first time through, this skips
					* blank line at at last header.
					*/
	
	r = Slrn_Ignore_Quote_Regexp;
	
	while (*r != NULL)
	  {
	     SLRegexp_Type *re;
	     
	     re = *r;
	     if ((re->min_length <= min_len) &&
		 (NULL != SLang_regexp_match (b, min_len, re)))
	       {
		  last->flags |= QUOTE_LINE;
		  while (l != NULL)
		    {
		       b = (unsigned char *) l->buf;
		       min_len = strlen ((char *) b);
		       if ((re->min_length <= min_len)
			   && (NULL == SLang_regexp_match (b, min_len, re)))
			 {
			    
			    /* Here it might be a good idea to add:
			     * l = l->prev;
			     */
			    break;
			 }
		       l->flags |= QUOTE_LINE;
		       l = l->next;
		    }
		  break;
	       }
	     r++;
	  }
     }
}

static void mark_signature (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   int nmax = 10;
   
   if (l == NULL) return;
   /* go to end of article */
   while (l->next != NULL) l = l->next;
   
   /* skip back untill "-- " seen.  Assume that it is not more than
    * 10 lines long.
    */
   
   while ((l != NULL) && nmax--
	  && ((*l->buf != '-')
	      || strcmp (l->buf, "-- ")))
     l = l->prev;
   
   if (nmax == -1) return;
   
   while (l != NULL)
     {
        l->flags |= SIGNATURE_LINE;
        l->flags &= ~(
		      QUOTE_LINE  /* if in a signature, not a quote */
#if SLRN_HAS_SPOILERS
		      | SPOILER_LINE     /* not a spoiler */
#endif
		      );
	l = l->next;
     }
}

static Slrn_Article_Line_Type *Extract_Header_Header_Line;

/* This does not take continued lines into account.  This is a bug. */
static char *extract_header (char *hdr, unsigned int len)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   
   while ((l != NULL)
	  && (*l->buf != 0))
     {
	if (0 == slrn_case_strncmp ((unsigned char *) hdr,
				    (unsigned char *) l->buf, len))
	  {
	     if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
	       unwrap_line (l->next);
	     Extract_Header_Header_Line = l;
	     return l->buf + len;
	  }
	l = l->next;
     }
   Extract_Header_Header_Line = NULL;
   return NULL;
}


/*{{{ wrap article functions  */
/* The input line is assumed to be the first wrapped portion of a line.  For
 * example, if a series of lines denoted as A/B is wrapped: A0/A1/A2/B0/B1,
 * then to unwrap A, A1 is passed and B0 is returned.
 */
static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Line_Type *l)
{
   char *b;
   Slrn_Article_Line_Type *next, *ll;
	     
   ll = l->prev;
   b = ll->buf;
   do 
     {
	b += strlen (b);
	strcpy (b, l->buf + 1);   /* skip the space at beginning of
				   * the wrapped line. */
	next = l->next;
	SLFREE (l->buf);
	SLFREE (l);
	if (l == Current_Line) Current_Line = ll;
	l = next;
     }
   while ((l != NULL) && (l->flags & WRAPPED_LINE));
	     
   ll->next = l;
   if (l != NULL) l->prev = ll;
   return l;
}

int Slrn_Wrap_Mode = 3;
static void wrap_article (void)
{
   unsigned int len;
   unsigned char *buf, ch;
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   int was_wrapped = 0;
   unsigned int wrap_mode = Slrn_Wrap_Mode;

   if (Slrn_Prefix_Arg_Ptr != NULL) wrap_mode = 0x7F;
   Slrn_Prefix_Arg_Ptr = NULL;
   
   if (Header_Showing == NULL) return;
   /* First scan through to see if it needs unwrapped. */
   while (l != NULL)
     {
	if (l->flags & WRAPPED_LINE)
	  {
	     l = unwrap_line (l);
	     was_wrapped = 1;
	  }
	else l = l->next;
     }
   
   l = Slrn_Article_Lines;

   if (was_wrapped == 0) while (l != NULL)
     {
	if (l->flags & HEADER_LINE) 
	  {
	     if ((wrap_mode & 1) == 0)
	       {
		  l = l->next;
		  continue;
	       }
	  }
	else if (l->flags & QUOTE_LINE)
	  {
	     if ((wrap_mode & 2) == 0)
	       {
		  l = l->next;
		  continue;
	       }
	  }

	len = 0;
	buf = (unsigned char *) l->buf;
	ch = *buf;
	while (ch != 0)
	  {
	     if (ch == '\t') len++;
	     else if (((int) (ch & 0x7F) < ' ') 
		  || (ch == 127))
	       {
		  len += 2;
		  if (ch & 0x80) len++;
	       }
	     else len++;
	     if (len > SLtt_Screen_Cols)
	       {
		  Slrn_Article_Line_Type *new_l;
		  
		  /* Start wrapped lines with a space */
		  buf--;
		  ch = *buf;
		  *buf = ' ';
		  
		  if ((NULL == (new_l = (Slrn_Article_Line_Type *)SLMALLOC(sizeof (Slrn_Article_Line_Type))))
		      || (NULL == (new_l->buf = SLmake_string((char *)buf))))
		    
		    {
		       if (new_l != NULL) SLFREE (new_l);
		       slrn_error ("Malloc error.");
		       return;
		    }
		  *buf++ = ch;
		  *buf = 0;
		  
		  /* We are not expanding tabs so, get rid of them */
		  buf = (unsigned char *) l->buf;
		  while ((ch = *buf) != 0) 
		    {
		       if (ch == '\t') *buf = ' ';
		       buf++;
		    }
		  
		  new_l->next = l->next;
		  new_l->prev = l;
		  l->next = new_l;
		  if (new_l->next != NULL) new_l->next->prev = new_l;

		  new_l->flags = l->flags | WRAPPED_LINE;
		  
		  l = new_l;
		  buf = (unsigned char *) new_l->buf;
		  len = 0;
	       }
	     else buf++;
	     
	     ch = *buf;
	  }
	l = l->next;
     }
   Slrn_Full_Screen_Update = 1;
   find_article_line_number ();
   count_article_lines ();
}

/*}}}*/

Slrn_Article_Line_Type *slrn_search_article (char *string,
					     char **ptrp,
					     int is_regexp,
					     int set_current_flag)
{
   SLsearch_Type st;
   Slrn_Article_Line_Type *l;
   char *ptr;
   int ret;
   SLRegexp_Type *re;
   
   if (-1 == (ret = select_article (1)))
     return NULL;

   if (is_regexp)
     {
	re = slrn_compile_regexp_pattern (string);
	if (re == NULL)
	  return NULL;
     }
   else SLsearch_init (string, 1, 0, &st);

   if (ret == 1)
     {
	art_update_screen ();
	slrn_smg_refresh ();
	l = Top_Line->next;
     }
   else
     l = Slrn_Article_Lines;
   

   
   while (l != NULL)
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     if (is_regexp)
	       ptr = (char *) slrn_regexp_match (re, l->buf);
	     else
	       ptr = (char *) SLsearch ((unsigned char *) l->buf,
					(unsigned char *) l->buf + strlen (l->buf),
					&st);
	     
	     if (ptr != NULL)
	       {
		  if (ptrp != NULL) *ptrp = ptr;
		  if (set_current_flag)
		    {
		       Current_Line = l;
		       find_article_line_number ();
		    }
		  break;
	       }
	  }
	l = l->next;
     }
      
   return l;
}

#define URL_SIG_STR "://"
#define BROWSER_BUFLEN 4096
static char *find_url (char *buf)
{
   Slrn_Article_Line_Type *l;
   char *ptr, *tmp, ch, *s;
   unsigned int len;
   
   if (NULL == (l = slrn_search_article (URL_SIG_STR, &ptr, 0, 1)))
     return NULL;
   
   s = l->buf;
   
   if (ptr > s) ptr--;
   
   while ((ptr >= s) 
	  && isalpha (*ptr))
     ptr--;
   
   ptr++;
   tmp = ptr;
     
   while ((ch = *ptr) != 0)
     {
	if (!isalpha(ch)
	    && ((ch == ' ')
		|| (ch == '\t')
		|| (ch == '\n')
		|| (ch == '\"')
		|| (ch == '}')
		|| (ch == '{')
		|| (ch == ')')
		|| (ch == ',')
		|| (ch == ';')
		|| (ch == '>')))
	  break;
	ptr++;
     }
   
   len = (unsigned int) (ptr - tmp);
   if (len >= BROWSER_BUFLEN) len = BROWSER_BUFLEN - 1;
   strncpy (buf, tmp, len);
   buf[len] = 0;
   return buf;
}



static void browse_url (void)
{
   char command [BROWSER_BUFLEN];
   char url[BROWSER_BUFLEN];
   char *browser, *has_percent;
   int reinit;
   
   if ((NULL == getenv ("DISPLAY")) || (NULL == Slrn_X_Browser))
     browser = Slrn_NonX_Browser;
   else browser = Slrn_X_Browser;
        
   if (browser == NULL)
     {
	slrn_error ("No Web Browser has been defined.");
	return;
     }
   
   /* Perform a simple-minded syntax check. */
   has_percent = slrn_strchr (browser, '%');
   if (has_percent != NULL)
     {
	if ((has_percent[1] != 's') 
	    || ((has_percent != browser) && (*(has_percent - 1) == '\\')))
	  has_percent = NULL;
     }
   
   if (NULL == find_url (url))
     {
	slrn_error ("No URLs found.");
	return;
     }
   
   if (slrn_read_input ("Browse (^G aborts): ", url, 0) <= 0)
     {
	slrn_error ("Aborted.");
	return;
     }
  
   if (has_percent != NULL)
     sprintf (command, browser, url);
   else
     sprintf (command, "%s %s", browser, url);

   reinit = (browser != Slrn_X_Browser);
   slrn_posix_system (command, reinit);
   /* if (reinit) slrn_redraw (); */
}

static void article_search (void)
{
   static char search_str[256];
   Slrn_Article_Line_Type *l;
   
   if (slrn_read_input ("Search: ", search_str, 0) <= 0) return;
   
   l = slrn_search_article (search_str, NULL, 0, 1);
   
   if (l == NULL) slrn_error ("Not found.");
}

/*}}}*/
/*{{{ current article movement functions */


static int art_lineup_n (int n)
{
   Slrn_Article_Line_Type *l;
   int m;

   if (select_article (1) <= 0) return 0;
   
   l = Current_Line;
   m = -1;
   while ((n >= 0) && (l != NULL))
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     n--;
	     m++;
	     Current_Line = l;
	  }
	l = l->prev;
     }
   
   /* This should ALWAYS be the case. */
   if (m != -1)
     {
	Article_Line_Number -= m;
     }
   
   return m;
}

static void art_pageup (void)
{
   art_lineup_n (Article_Window_Height - 1);
}

static void art_lineup (void)
{
   art_lineup_n (1);
}

static void art_bob (void)
{
   while (1000 == art_lineup_n (1000));
}


static int art_linedn_n (int n)
{
   Slrn_Article_Line_Type *l;
   int m;
   
   if (select_article (1) <= 0)
     return 0;

   l = Current_Line;
   m = -1;
   while ((n >= 0) && (l->next != NULL))
     {
	if ((l->flags & HIDDEN_LINE) == 0)
	  {
	     n--;
	     m++;
	     Current_Line = l;
	  }
	l = l->next;
     }
   
   if (m != -1)
     {
	Article_Line_Number += m;
     }
   return m;
}


static void art_linedn (void)
{
   art_linedn_n (1);
}

static void art_eob (void)
{
   while (art_linedn_n (10000) > 0)
     ;
   (void) art_lineup_n (Article_Window_Height / 2);
}

/*}}}*/

/*{{{ Tag functions */

typedef struct
{
   Slrn_Header_Type **headers;
   unsigned int max_len;
   unsigned int len;
}
Num_Tag_Type;

static Num_Tag_Type Num_Tag_List;

static void free_tag_list (void)
{
   if (Num_Tag_List.headers != NULL)
     {
	SLFREE (Num_Tag_List.headers);
	Num_Tag_List.headers = NULL;
	Num_Tag_List.len = Num_Tag_List.max_len = 0;
     }
}

void slrn_goto_num_tagged_header (int *nump)
{
   unsigned int num;
   
   num = (unsigned int) *nump;
   num--;
   
   if (num >= Num_Tag_List.len)
     return;
   
   if (Num_Tag_List.headers == NULL)
     return;
   
   slrn_goto_header (Num_Tag_List.headers[num], 0);
   Slrn_Full_Screen_Update = 1;
}

static void num_tag_header (void)
{
   char *memory_error = "Out of memory.";
   unsigned int len;
   
   uncollapse_this_thread (Slrn_Current_Header);
   if (Num_Tag_List.headers == NULL)
     {
	Slrn_Header_Type **headers;
	unsigned int max_len = 20;
	
	if (NULL == (headers = (Slrn_Header_Type **) SLMALLOC (max_len * sizeof (Slrn_Header_Type))))
	  {
	     slrn_error (memory_error);
	     return;
	  }
	Num_Tag_List.max_len = max_len;
	Num_Tag_List.headers = headers;
	Num_Tag_List.len = 0;
     }
   
   if (Num_Tag_List.max_len == Num_Tag_List.len)
     {
	Slrn_Header_Type **headers = Num_Tag_List.headers;
	unsigned int max_len = Num_Tag_List.max_len + 20;
	if (NULL == (headers = (Slrn_Header_Type **) SLREALLOC (headers,
								max_len * sizeof (Slrn_Header_Type))))
	  {
	     slrn_error (memory_error);
	     return;
	  }
	Num_Tag_List.max_len = max_len;
	Num_Tag_List.headers = headers;
     }
   
   Slrn_Full_Screen_Update = 1;
   if ((Slrn_Current_Header->flags & HEADER_NTAGGED) == 0)
     {
	Num_Tag_List.headers[Num_Tag_List.len] = Slrn_Current_Header;
	Num_Tag_List.len += 1;
	Slrn_Current_Header->tag_number = Num_Tag_List.len;
	Slrn_Current_Header->flags |= HEADER_NTAGGED;
	(void) slrn_header_down_n (1, 0);
	return;
     }
   
   /* It is already tagged.  So, what do we do.  The most sensible thing to
    * do is to simply give this header the last number and renumber the others
    * that follow it.  If it is the last one, untag it.
    */
   if (Slrn_Current_Header->tag_number == Num_Tag_List.len)
     {
	Num_Tag_List.len -= 1;
	Slrn_Current_Header->tag_number = 0;
	Slrn_Current_Header->flags &= ~HEADER_NTAGGED;
	return;
     }
   
   for (len = Slrn_Current_Header->tag_number + 1; len <= Num_Tag_List.len; len++)
     {
	Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
	Num_Tag_List.headers[len - 2] = h;
	h->tag_number -= 1;
     }
   Num_Tag_List.headers[len - 2] = Slrn_Current_Header;
   Slrn_Current_Header->tag_number = len - 1;
}

static void num_untag_headers (void)
{
   unsigned int len;
   for (len = 1; len <= Num_Tag_List.len; len++)
     {
	Slrn_Header_Type *h = Num_Tag_List.headers[len - 1];
	h->flags &= ~HEADER_NTAGGED;
	h->tag_number = 0;
     }
   Num_Tag_List.len = 0;
   Slrn_Full_Screen_Update = 1;
}


static void toggle_one_header_tag (Slrn_Header_Type *h)
{
   if (h == NULL) return;
   if (h->flags & HEADER_TAGGED)
     {
	h->flags &= ~HEADER_TAGGED;
     }
   else h->flags |= HEADER_TAGGED;
}


static void toggle_header_tag (void)
{
   if (Slrn_Prefix_Arg_Ptr != NULL)
     {
	Slrn_Header_Type *h;
	
	Slrn_Prefix_Arg_Ptr = NULL;
	h = Headers;
	while (h != NULL)
	  {
	     h->flags &= ~HEADER_TAGGED;
	     h = h->next;
	  }
	Slrn_Full_Screen_Update = 1;
	return;
     }

   if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
       || (Slrn_Current_Header->child == NULL)/* At top with no child */
       /* or at top with child showing */
       || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
     {
	toggle_one_header_tag (Slrn_Current_Header);
     }
   else
     {
	for_this_tree (Slrn_Current_Header, toggle_one_header_tag);
     }
   (void) slrn_header_down_n (1, 0);
   Slrn_Full_Screen_Update = 1;
}

int slrn_prev_tagged_header (void)
{
   Slrn_Header_Type *h = Slrn_Current_Header;
   
   if (h == NULL) return 0;
   
   while (h->prev != NULL)
     {
	h = h->prev;
	if (h->flags & HEADER_TAGGED)
	  {
	     slrn_goto_header (h, 0);
	     return 1;
	  }
     }
   return 0;
}

int slrn_next_tagged_header (void)
{
   Slrn_Header_Type *h = Slrn_Current_Header;
   
   if (h == NULL) return 0;
   
   while (h->next != NULL)
     {
	h = h->next;
	if (h->flags & HEADER_TAGGED)
	  {
	     slrn_goto_header (h, 0);
	     return 1;
	  }
     }
   return 0;
}




/*}}}*/
/*{{{ Header specific functions */


static Slrn_Header_Type *find_header_from_serverid (int id)
{
   Slrn_Header_Type *h;
   
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if (h->number > id) return NULL;
	if (h->number == id) break;
	h = h->real_next;
     }
   return h;
}

static void kill_cross_references (Slrn_Header_Type *h)
{
   char *b;
   char group[256], *g;
   long num;
   
   if (h->xref == NULL)
     {
	if ((Header_Showing != h)
	    || (NULL == (b = extract_header ("Xref:", 5))))
	  {
	     return;
	  }
     }
   else b = h->xref;
   
   
   /* The format appears to be:
    * Xref: machine group:num group:num...
    */
   
   /* skip machine name */
   while (*b > ' ') b++;
   
   while (*b != 0)
     {
	while (*b == ' ') b++;
	if (*b == 0) break;
	
	/* now we are looking at the groupname */
	g = group;
	while (*b && (*b != ':')) *g++ = *b++;
	if (*b++ == 0) break;
	*g = 0;
	num = atoi (b);
	while ((*b <= '9') && (*b >= '0')) b++;
	if ((num != h->number)
	    || strcmp (group, Slrn_Current_Group_Name))
	  slrn_mark_article_as_read (group, num);
     }
}

static void find_header_line_number (void)
{
   Slrn_Header_Type *h = Headers;
   Header_Number = 1;
   
   while (h != Slrn_Current_Header)
     {
	if ((h->flags & HEADER_HIDDEN) == 0)
	  Header_Number++;
	h = h->next;
     }
   Slrn_Full_Screen_Update = 1;
}


static void for_all_headers (void (*func)(Slrn_Header_Type *), int all)
{
   Slrn_Header_Type *h, *end;
   
   if (func == NULL) return;
   
   if (all) end = NULL; else end = Slrn_Current_Header;
   
   h = Headers;
   
   while (h != end)
     {
	(*func)(h);
	h = h->next;
     }
}


int slrn_goto_header (Slrn_Header_Type *header, int read_flag)
{
   Slrn_Header_Type *h = Slrn_First_Header;
   
   while ((h != NULL) && (h != header))
     h = h->real_next;

   if (h == NULL) return -1;
   
   if (h->flags & HEADER_HIDDEN) uncollapse_this_thread (h);
   Slrn_Current_Header = h;
   find_header_line_number ();
   
   if (read_flag) select_article (1);
   return 0;
}


/*{{{ parse_from  */

static char *parse_from (char *from)
{
   static char buf[256], *b;
   char ch, ch1;
   
   /* Here I am assume the following:
    * 1.  parenthesis and double quotes are comments.
    * 2.  If a name is in <> it is given precedence
    */
   
   if (from == NULL) return NULL;
   from = slrn_skip_whitespace (from);
   
   b = buf;
   
   /* strip comments */
   while ((ch = *from++) != 0)
     {
	if (ch == '"')
	  {
	     ch1 = ch;
	     while (((ch = *from++) != 0) && (ch != ch1)) 
	       ;
	     if (ch == 0) return NULL;
	  }
	else if (ch == '(')
	  {
	     int nump = 1;
	     ch1 = ')';
	     
	     while ((ch = *from++) != 0)
	       {
		  if (ch == '(') nump++;
		  else if (ch == ch1)
		    {
		       nump--;
		       if (nump == 0) break;
		    }
	       }
	     if (ch == 0) return NULL;
	  }
	else if (ch == ')') return NULL;
	else if (ch != ' ') *b++ = ch;
     }
   *b = 0;
   
   from = b = buf;
   
   while ((ch = *from++) != 0)
     {
	if (ch == '<')
	  {
	     while (((ch = *from++) != 0) && (ch != '>'))
	       {
		  *b++ = ch;
	       }
	     if (ch == 0) return NULL;
	     *b = 0;
	     break;
	  }
     }
   return buf;
}


/*}}}*/

/*{{{ fixup_realname */
static void fixup_realname (Slrn_Header_Type *h)
{
   char *p, *pmin, *pmax;
   if ((h->realname == NULL) || (h->realname_len == 0)) return;
   pmin = h->realname;
   p = pmin + (h->realname_len - 1);
   while ((p >= pmin) && ((*p == ' ') || (*p == '\t'))) p--;
   p++;
   h->realname_len = (int) (p - pmin);
   
   /* Check if realname is enclosed in "" and if so strip them off.
    * Leave "" alone though.
    */
   if ((p > pmin + 2) &&
       (*pmin == '"') && (*(p - 1) == '"'))
     {
	h->realname++;
	h->realname_len -= 2;
     }
   
   /* Look for a character in the range [A-Za-z].  If none, found use from.
    */
   p = h->realname;
   pmax = p + h->realname_len;
   while (p < pmax)
     {
	char ch = *p;
	
	if (isalpha (ch)) return;
	p++;
     }
   
   h->realname = parse_from (h->from);
   
   if (h->realname == NULL)
     h->realname = h->from;
   
   h->realname_len = strlen (h->realname);
}

/*}}}*/

/*{{{ get_header_real_name */

static void get_header_real_name (Slrn_Header_Type *h)
{
   register char *f, *f0, *f1;
   register char ch;
   char *from = h->from;
   int n;
   
   f = from;
   while (((ch = *f) != 0) && (ch != '<') && (ch != '(')) f++;
   if (ch == '(')
     {
	/* Look for a '<'.  There is some probability that this is of the
	 * form:  John Doe (800) 555-1212 <doe@nowhere.com>
	 */
	f0 = f;
	f1 = f + strlen (f);
	while ((f1 > f) && (*f1 != ')') && (*f1 != '<')) f1--;
	if (*f1 == '<')
	  {
	     ch = '<';
	     f = f1;
	  }
     }
   
   if (ch != '(')
     {
	if (ch == '<')
	  {
	     f1 = slrn_skip_whitespace (from);
	     if (*f1 == '<')
	       {
		  f = f1 + 1;
		  from = f;
		  while (((ch = *f) != 0) && (ch != '>')) f++;
	       }
	  }
	h->realname = from;
	h->realname_len = (int) (f - from);
	fixup_realname (h);
	return;
     }
   
   f1 = f;
   f0 = f + 1;
   f0 = slrn_skip_whitespace (f0);
   if (*f0 == ')')
     {
	h->realname = from;
	h->realname_len = (int) (f1 - from);
	fixup_realname (h);
	return;
     }
   f = f0;
   n = 0;
   while ((ch = *f) != 0)
     {
	if (ch == '(') n++;
	else if (ch == ')')
	  {
	     if (n == 0) break;
	     n--;
	  }
	f++;
     }
   h->realname = f0;
   h->realname_len = (int) (f - f0);
   fixup_realname (h);
}

/*}}}*/

static void extract_real_names (void)
{
   Slrn_Header_Type *h = Headers;
   while (h != NULL)
     {
	get_header_real_name (h);
	h = h->next;
     }
}

static Slrn_Header_Type *find_header_from_msgid (char *r0, char *r1)
{
   unsigned long hash;
   Slrn_Header_Type *h;
   unsigned int len;
   len = (unsigned int) (r1 - r0);
   hash = slrn_compute_hash ((unsigned char *) r0, (unsigned char *) r1);
   
   h = Header_Table[hash % HEADER_TABLE_SIZE];
   while (h != NULL)
     {
	if (!slrn_case_strncmp ((unsigned char *) h->msgid,
				(unsigned char *) r0,
				len))
	  break;
	
	h = h->hash_next;
     }
   return h;
}

Slrn_Header_Type *slrn_find_header_with_msgid (char *msgid)
{
   return find_header_from_msgid (msgid, msgid + strlen (msgid));
}

static void goto_article (void)
{
   Slrn_Header_Type *h;
   int want_n;
   
   if (-1 == slrn_read_integer ("Goto article: ", NULL, &want_n))
     return;
   
   h = Headers;
   while (h != NULL)
     {
	if (h->number == want_n)
	  {
	     Slrn_Full_Screen_Update = 1;
	     if (h->flags & HEADER_HIDDEN) uncollapse_this_thread (h);
	     Slrn_Current_Header = h;
	     find_header_line_number ();
	     return;
	  }
	
	h = h->next;
     }
   
   slrn_error ("Article not found.");
}

/*}}}*/

/*{{{ read_article */

static int read_article (Slrn_Header_Type *h, int kill_refs, int do_mime)
{
   Slrn_Article_Line_Type *l;
   char buf[NNTP_BUFFER_SIZE], *b, *b1, ch;
   unsigned int len;
   int n = h->number;
   Slrn_Header_Type *last_header_showing;
   int num_lines_update;
   
   last_header_showing = Header_Showing;
   
   if (Header_Showing == h)
     {
#if SLRN_HAS_MIME
	if (Slrn_Use_Mime == 0)
	  return 0;
	if ((do_mime && Slrn_Mime_Was_Parsed)
	    || ((do_mime == 0) && (Slrn_Mime_Was_Modified == 0)))
#endif
	  return 0; /* The cached copy is good */
     }
   
   if (h->tag_number) slrn_message ("#%2d/%-2d: Retrieving... %s", 
				    h->tag_number, Num_Tag_List.len, 
				    h->subject);
   else slrn_message ("Reading...");
   
   slrn_smg_refresh ();
   
   slrn_set_suspension (1);

   if (Slrn_Server_Obj->sv_select_article (n, h->msgid) < 0)
     {
	slrn_set_suspension (0);
	slrn_error ("Article %d unavailable.", n);
	
	if (kill_refs && ((h->flags & HEADER_READ) == 0))
	  {
	     kill_cross_references (h);
	     h->flags |= HEADER_READ;
	  }
	return -1;
     }
   
   
   At_End_Of_Article = NULL;
   
   if (n != -1) Group_Modified = 1;
   Do_Rot13 = 0;
   HScroll = 0;
   Slrn_Full_Screen_Update = 1;
   
   free_article ();
   
   Article_Total_Lines = 0;

   if ((num_lines_update = Slrn_Reads_Per_Update) < 5)
     {
	if (h->lines < 200)
	  num_lines_update = 20;
	else 
	  num_lines_update = 50;
     }
   
   while (Slrn_Server_Obj->sv_read_line(buf, sizeof(buf)) != NULL)
     {
	if (SLang_Error == USER_BREAK)
	  {
	     slrn_error ("Article transfer aborted.");
	     Slrn_Server_Obj->sv_close ();
	     if (Slrn_Article_Lines == NULL)
	       {
		  slrn_set_suspension (0);
		  return -1;
	       }
	     break;
	  }

	Article_Total_Lines++;
	if ((1 == (Article_Total_Lines % num_lines_update))
	    /* Just so the ratio does not confuse the reader because of the
	     * header lines...
	     */
	    && (Article_Total_Lines < h->lines))
	  {
	     if (h->tag_number)
	       slrn_message ("#%2d/%-2d: Read %4d/%-4d lines (%s)",
			     h->tag_number, Num_Tag_List.len,
			     Article_Total_Lines, h->lines, h->subject);
	     else
	       slrn_message ("Read %d/%d lines so far...", 
			     Article_Total_Lines, h->lines);
	     slrn_smg_refresh ();
	  }
	
	len = strlen (buf);
	
	if ((NULL == (l = (Slrn_Article_Line_Type *) SLMALLOC (sizeof(Slrn_Article_Line_Type))))
	    || (NULL == (l->buf = (char *) SLMALLOC (len + 1))))
	  {
	     slrn_exit_error ("Memory allocation error.");
	  }
	
	/* here I am going to remove _^H combinations.  Later, it will be a
	 * good idea to perform the implied underlining
	 */
	b1 = l->buf;
	b = buf;
	
	if ((*b == '.') && (*(b + 1) == '.')) b++;
	
	while (0 != (ch = *b++))
	  {
	     if ((ch == '_') && (*b == '\b'))
	       {
		  b++;
	       }
	     else *b1++ = ch;
	  }
	*b1 = 0;
	
	l->next = l->prev = NULL;
	l->flags = 0;
	
	if (Slrn_Article_Lines == NULL)
	  {
	     Slrn_Article_Lines = l;
	  }
	else
	  {
	     l->next = Current_Line->next;
	     l->prev = Current_Line;
	     Current_Line->next = l;
	     
	     if (l->next != NULL) l->next->prev = l;
	  }
	Current_Line = l;
     }
   Current_Line = Slrn_Article_Lines;  Article_Line_Number = 1;
   Slrn_Message_Present = 0;
   
   if (kill_refs && ((h->flags & HEADER_READ) == 0))
     {
	kill_cross_references (h);
	h->flags |= HEADER_READ;
     }

   /* Do this now so that header lines are marked.  Mime processing 
    * assumes this.
    */
   if (h == Slrn_Current_Header) hide_art_headers ();

#if SLRN_HAS_MIME
   if (Slrn_Use_Mime)
     {
	/* Note: the mime routines assume that the article flags are valid.
	 * That is, a header line is given by line->flags & HEADER_LINE
	 */
	slrn_mime_article_init ();
	if (do_mime) slrn_mime_process_article ();
     }
#endif

   if (h == Slrn_Current_Header)
     {
	mark_quotes ();
#if SLRN_HAS_SPOILERS
	if (Slrn_Spoiler_Char) mark_spoilers ();
#endif
	mark_signature ();
	hide_quotes ();
     }
   
   if (last_header_showing != h)
     {
	Last_Read_Header = last_header_showing;
     }
   Header_Showing = h;
   
   if (h == Slrn_Current_Header)
     {
	if (Slrn_Wrap_Mode & 0x4) wrap_article ();
	if (Last_Read_Header == NULL)
	  Last_Read_Header = h;
     }

   slrn_set_suspension (0);
   return 0;
}

/*}}}*/
/*{{{ reply, reply_cmd, forward, followup */

/*{{{ reply */
static void reply (void)
{
   char *msgid, *from, *subject, *f;
   Slrn_Article_Line_Type *l;
   FILE *fp;
   char file[256];
   
   uncollapse_this_thread (Slrn_Current_Header);
   
   if (read_article (Slrn_Current_Header, 1, 1) < 0) return;

   /* Check for FQDN.  If it appear bogus, warn user */
   
   if (NULL == ( from = extract_header ("Reply-To: ", 10)))
     {
	from = extract_header ("From: ", 6);
     }
   from = parse_from (from);
   
   if ((from == NULL) 
       || (NULL == (f = slrn_strchr (from, '@')))
       || (f == from)
       || (0 == slrn_is_fqdn (f + 1)))
     {
	if (0 == slrn_get_yesno (1, "%s appears invalid.  Continue anyway",
				 ((from == NULL) ? "Email address" : from)))
	  return;
     }
   
   if (Slrn_Use_Tmpdir)
     {
	fp = slrn_open_tmpfile (file, "w");
     }
   else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
   
   if (NULL == fp)
     {
	slrn_error ("Unable to open %s for writing.", file);
	return;
     }
   
   /* parse header */
   msgid = extract_header ("Message-ID: ", 12);
   subject = extract_header ("Subject: ", 9);
   
   fprintf (fp, "To: %s\nSubject: %s\nIn-Reply-To: %s\n",
	    from == NULL ? "" : from,
	    subject == NULL ? "" : subject,
	    msgid);
   
#ifdef __os2__
   fprintf (fp, "From: %s@%s (%s)\n", 
	    Slrn_User_Info.username, Slrn_User_Info.host,
	    Slrn_User_Info.realname);
#endif
   
   if (0 != *Slrn_User_Info.replyto)
     fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
   
#if 0
   newsgroups = extract_header ("Newsgroups: ", 12);
   if (newsgroups != NULL)
     fprintf (fp, "Newsgroups: %s\n", newsgroups);
#endif
   
   fputs ("\n", fp);
   
   fprintf (fp, "In article %s, you wrote:\n", msgid == NULL ? "" : msgid);
   l = Slrn_Article_Lines;
   if (Slrn_Prefix_Arg_Ptr == NULL)
     {
	while ((l != NULL) && (*l->buf != 0)) l = l->next;
	if (l != NULL) l = l->next;
     }
   
   while (l != NULL)
     {
	fprintf (fp, "%s%s\n", Slrn_Quote_String, l->buf);
	l = l->next;
     }
   slrn_add_signature (fp);
   slrn_fclose (fp);
   
   slrn_mail_file (file, 1, 5, from, subject);
   if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
}

/*}}}*/
/*{{{ reply_cmd */
static void reply_cmd (void)
{
   uncollapse_this_thread (Slrn_Current_Header);
   
   if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
   
   if (Slrn_User_Wants_Confirmation
       && (slrn_get_yesno (1, "Are you sure you want to reply") <= 0))
     return;
   reply ();
}


/*}}}*/
/*{{{ forward */
static void forward (void)
{
   char *subject;
   Slrn_Article_Line_Type *l;
   FILE *fp;
   char file[256];
   char to[256];
   int n;
   
   uncollapse_this_thread (Slrn_Current_Header);
   if (read_article (Slrn_Current_Header, 1, 1) < 0) return;
   
   *to = 0;
   if (slrn_read_input ("Forward to (^G aborts): ", to, 1) <= 0)
     {
	slrn_error ("Aborted.  An email address is required.");
	return;
     }
   
   if (Slrn_Use_Tmpdir)
     {
	fp = slrn_open_tmpfile (file, "w");
     }
   else fp = slrn_open_home_file (SLRN_LETTER_FILENAME, "w", file, 0);
   
   if (fp == NULL)
     {
	slrn_error ("Unable to open %s for writing.", file);
	return;
     }
   
   
   subject = extract_header ("Subject: ", 9);
   
   fprintf (fp, "To: %s\nSubject: %s\n",
	    to, subject == NULL ? "" : subject);
   
#ifdef __os2__
   fprintf (fp, "From: %s@%s (%s)\n", 
	    Slrn_User_Info.username, Slrn_User_Info.host,
	    Slrn_User_Info.realname);
#endif
   
   if (0 != *Slrn_User_Info.replyto)
     fprintf (fp, "Reply-To: %s (%s)\n", 
	      Slrn_User_Info.replyto, Slrn_User_Info.realname);
   putc ('\n', fp);
   
   l = Slrn_Article_Lines;
   while (l != NULL)
     {
	fprintf (fp, "%s\n", l->buf);
	l = l->next;
     }
   slrn_fclose (fp);
   
   n = slrn_get_yesno (1, "Edit the message before sending");
   
   if (n >= 0)
     slrn_mail_file (file, n, 3, to, subject);

   if (Slrn_Use_Tmpdir) slrn_delete_file (file);
}

/*}}}*/
/*{{{ insert_followup_format */
static void insert_followup_format (char *f, FILE *fp)
{
   char ch, *s, *smax;
   
   while ((ch = *f++) != 0)
     {
	if (ch != '%')
	  {
	     putc (ch, fp);
	     continue;
	  }
	s = smax = NULL;
	ch = *f++;
	if (ch == 0) break;
	
	switch (ch)
	  {
	   case 's':
	     s = Slrn_Current_Header->subject;
	     break;
	   case 'm':
	     s = Slrn_Current_Header->msgid;
	     break;
	   case 'r':
	     s = Slrn_Current_Header->realname;
	     smax = s + Slrn_Current_Header->realname_len;
	     break;
	   case 'f':
	     s = parse_from (Slrn_Current_Header->from);
	     break;
	   case 'n':
	     s = extract_header ("Newsgroups: ", 12);
	     break;
	   case 'd':
	     s = Slrn_Current_Header->date;
	     break;
	   case '%':
	   default:
	     putc (ch, fp);
	  }
	
	if (s == NULL) continue;
	if (smax == NULL) fputs (s, fp);
	else fwrite (s, 1, (unsigned int) (smax - s), fp);
     }
}

/*}}}*/
/*{{{ followup */

/* If prefix arg is 1, insert all headers.  If it is 2, insert all headers
 * but do not quote text nor attach signature.  2 is good for re-posting.
 */
static void followup (void)
{
   char *msgid, *from, *newsgroups, *subject, *xref, *quote_str;
   Slrn_Article_Line_Type *l;
   FILE *fp;
   char file[256];
   unsigned int n;
   int prefix_arg;
   int perform_cc;

   if (Slrn_Post_Obj->po_can_post == 0)
     {
	slrn_error ("Posting not allowed by server.");
	return;
     }
   
   uncollapse_this_thread (Slrn_Current_Header);
   if (read_article (Slrn_Current_Header, 1, 1) < 0) return;

   if (Slrn_User_Wants_Confirmation
       && (slrn_get_yesno (1, "Are you sure you want to followup") <= 0))
     return;
   
   
   if (NULL != (newsgroups = extract_header ("Followup-To: ", 13)))
     {
	newsgroups = slrn_skip_whitespace (newsgroups);
	if (!slrn_case_strncmp ((unsigned char *) newsgroups,
				(unsigned char *) "poster", 6))
	  {
	     if (slrn_get_yesno (1, "Do you want to reply to POSTER as poster prefers"))
	       {
		  reply ();
		  return;
	       }
	     newsgroups = NULL;
	  }
     }
   
   if (Slrn_Post_Obj->po_can_post == 0)
     {
	slrn_error ("Posting not allowed.");
	return;
     }

   if (Slrn_Prefix_Arg_Ptr != NULL)
     {
	prefix_arg = *Slrn_Prefix_Arg_Ptr;
	Slrn_Prefix_Arg_Ptr = NULL;
     }
   else prefix_arg = -1;
   
   if (newsgroups == NULL)
     {
	if (NULL == (newsgroups = extract_header ("Newsgroups: ", 12)))
	  newsgroups = "";
     }
   
   if (NULL == (from = extract_header ("Reply-To: ", 10)))
     {
	from = extract_header ("From: ", 6);
     }
   from = parse_from (from);
   
   if ((from == NULL) || (prefix_arg == 2))
     perform_cc = 0;
   else if (-1 == (perform_cc = Slrn_Auto_CC_To_Poster))
     perform_cc = slrn_get_yesno (1, "Cc message to poster");
   
   if (perform_cc)
     {
	char *ff;
	
	if ((NULL == (ff = slrn_strchr (from, '@')))
	    || (ff == from)
	    || (0 == slrn_is_fqdn (ff + 1))
	    || (strlen (ff + 1) < 5))
	  {
	     if (0 == slrn_get_yesno (1, "%s appears invalid.  CC anyway", from))
	       perform_cc = 0;
	  }
     }
   
   msgid = extract_header ("Message-ID: ", 12);
   
   if (NULL != (subject = extract_header ("Subject: ", 9)))
     {
	subject = slrn_skip_whitespace (subject);
	if (((*subject | 0x20) == 'r')
	    && ((*(subject + 1) | 0x20) == 'e')
	    && (*(subject + 2) == ':'))
	  {
	     subject = slrn_skip_whitespace (subject + 3);
	  }
     }
   else subject = "";
   
   if (Slrn_Use_Tmpdir)
     {
	fp = slrn_open_tmpfile (file, "w");
     }
   else fp = slrn_open_home_file (SLRN_FOLLOWUP_FILENAME, "w", file, 0);
   
   if (fp == NULL)
     {
	slrn_error ("Unable to open %s for writing.", file);
	return;
     }
   
   fprintf (fp, "Newsgroups: %s\nSubject: Re: %s\n",
	    newsgroups, subject);  n = 3;
   
   xref = extract_header("References: ", 12);
   if (msgid != NULL)
     {
	fputs ("References: ", fp);
	while (Extract_Header_Header_Line != NULL)
	  {
	     char ch;
	     
	     fprintf (fp, "%s ", xref);
	     
	     Extract_Header_Header_Line = Extract_Header_Header_Line->next;
	     if (Extract_Header_Header_Line == NULL) break;
	     
	     xref = Extract_Header_Header_Line->buf;
	     
	     ch = *xref++;
	     if ((ch != ' ') && (ch != '\t')) break;
	  }
	fprintf (fp, "%s\n", msgid);
	n++;
     }
   
   if (Slrn_User_Info.org != NULL)
     {
	fprintf (fp, "Organization: %s\n", Slrn_User_Info.org);
	n++;
     }
   
   if (perform_cc)
     {
	fprintf (fp, "Cc: %s\n", from);
	n++;
     }
   
   if (0 != *Slrn_User_Info.replyto)
     {
	fprintf (fp, "Reply-To: %s\n", Slrn_User_Info.replyto);
	n++;
     }
   
   fprintf (fp, "Followup-To: \n");
   n++;
   
   n += slrn_add_custom_headers (fp);
   
   fputs ("\n", fp);
   
   if (prefix_arg != 2)
     {
	insert_followup_format (Slrn_User_Info.followup_string, fp);
	fputs ("\n", fp);
     }
   n += 1;			       /* by having + 1, the cursor will be
					* placed on the first line of message.
					*/
   
   
   /* skip header */
   l = Slrn_Article_Lines;
   if (prefix_arg == -1)
     {
	while ((l != NULL) && (*l->buf != 0)) l = l->next;
	if (l != NULL) l = l->next;
     }
   
   if (prefix_arg == 2) quote_str = ""; else quote_str = Slrn_Quote_String;

   while (l != NULL)
     {
	if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
	  unwrap_line (l->next);
	
	fprintf (fp, "%s%s\n", quote_str, l->buf);
	
	l = l->next;
     }

   if (prefix_arg != 2) slrn_add_signature (fp);
   slrn_fclose (fp);
   
   if (slrn_edit_file (file, n) >= 0)
     {
	if (0 == slrn_post_file (file, from))
	  {
	     /* Success. */
	     if (Slrn_Last_Message_Id != NULL)
	       {
		  /* Later I want to actually get the article from the server
		   * and display it.  Of course I can only do it if I know
		   * the message-id.  If Slrn_Last_Message_Id is non-null, 
		   * then slrn generated the message-id and we can do it.
		   */
	       }
	  }
     }
   
   if (Slrn_Use_Tmpdir) (void) slrn_delete_file (file);
}

/*}}}*/


/*}}}*/

/*{{{ header movement functions */

int slrn_header_down_n (int n, int err)
{
   Slrn_Header_Type *h;
   int i;
   
   h = Slrn_Current_Header;
   if (h == NULL) return 0;
   i = 0;
   while (i < n)
     {
	h = h->next;
	while ((h != NULL) && (h->flags & HEADER_HIDDEN))
	  {
	     h = h->next;
	  }
	
	if (h == NULL)
	  {
	     if (err) slrn_error ("End of buffer.");
	     break;
	  }
	Header_Number++;
	Slrn_Current_Header = h;
	i++;
     }
   return i;
}

static void header_down (void)
{
   slrn_header_down_n (1, 1);
}

int slrn_header_up_n (int n, int err)
{
   Slrn_Header_Type *h;
   int i;
   
   h = Slrn_Current_Header;
   if (h == NULL) return 0;
   i = 0;
   while (i < n)
     {
	h = h->prev;
	while ((h != NULL) && (h->flags & HEADER_HIDDEN))
	  {
	     h = h->prev;
	  }
	
	if (h == NULL)
	  {
	     if (err) slrn_error ("Top of buffer.");
	     break;
	  }
	i++;
	Header_Number--;
	Slrn_Current_Header = h;
     }
   return i;
}

static void header_up (void)
{
   slrn_header_up_n (1, 1);
}

static void header_pageup (void)
{
   Slrn_Header_Type *g;
   int n;
   
   if (Top_Header != NULL)
     {
	Slrn_Full_Screen_Update = 1;
	/* Move to top of window, then go up one page */
	if (Header_Window_Height <= 2) 
	  {
	     header_up ();
	     return;
	  }
	
	n = 0;
	g = Slrn_Current_Header;
	while ((g != Top_Header) && (g != NULL))
	  {
	     g = g->prev;
	     if ((g != NULL) && (g->flags & HEADER_HIDDEN) == 0)
	       n++;
	  }
	
	if (g != NULL)
	  {
	     Slrn_Current_Header = g;
	     Header_Number -= n;
	     n = Header_Number;	       /* save this */
	     /* Now compute new top header */
	     slrn_header_up_n (Header_Window_Height - 2, 0);
	     Top_Header = Slrn_Current_Header;
	     Slrn_Current_Header = g; Header_Number = n;
	     return;
	  }
     }
   
   slrn_header_up_n (Header_Window_Height - 1, 1);
}


static void header_pagedn (void)
{
   Slrn_Header_Type *h, *g;
   int n;
   
   /* find bottom header and bring it to the top */
   if (Top_Header != NULL)
     {
	Slrn_Full_Screen_Update = 1;
	/* This fails if the window is only one line */
	n = Header_Window_Height - 1;
	if (n < 2)
	  {
	     slrn_header_down_n (n + 1, 0);
	     return;
	  }
	
	h = Top_Header;
	while ((h->next != NULL) && n)
	  {
	     h = h->next;
	     if (0 == (h->flags & HEADER_HIDDEN)) n--;
	  }
	
	if (n)
	  {
	     while (h->flags & HEADER_HIDDEN)
	       {
		  h = h->prev;
	       }
	  }
	else Top_Header = h;
	
	/* Now move the current to this header */
	g = Slrn_Current_Header;
	n = 0;
	while ((g != h) && (g != NULL))
	  {
	     g = g->next;
	     if (0 == (g->flags & HEADER_HIDDEN)) n++;
	  }
	
	if (g != NULL)
	  {
	     Slrn_Current_Header = g;
	     Header_Number += n;
	  }
	return;
     }
   
   slrn_header_down_n (Article_Window_Height, 0);
}

static void art_header_bob (void)
{
   Slrn_Current_Header = Headers;
   Header_Number = 1;
}

static void art_header_eob (void)
{
   while (1000 == slrn_header_down_n (1000, 0));
}

static int prev_unread (void)
{
   Slrn_Header_Type *h;
   int dn = 1;
   
   h = Slrn_Current_Header -> prev;
   
   while (h != NULL)
     {
	if (0 == (h->flags & HEADER_READ)) break;
	if ((h->flags & HEADER_HIDDEN) == 0)
	  {
	     dn++;
	  }
	h = h->prev;
     }
   
   if (h == NULL)
     {
	slrn_message ("No previous unread articles.");
	return 0;
     }
   
   
   Slrn_Current_Header = h;
   if (h->flags & HEADER_HIDDEN)
     {
	uncollapse_this_thread (h);
	find_header_line_number ();
     }
   else Header_Number -= dn;
   return 1;
}


static void art_pagedn (void)
{
   if (Slrn_Current_Header == NULL) return;

#if SLRN_HAS_SPOILERS
   if (Spoilers_Visible == Slrn_Current_Header)
     {
	show_spoilers ();
	return;
     }
#endif
   if (Article_Visible && (At_End_Of_Article == Slrn_Current_Header))
     {
	unsigned char ch = ' ';
	char *msg = NULL;
	
	At_End_Of_Article = NULL;
	
	if (Slrn_Current_Header->next == NULL)
	  {
	     if (Slrn_Query_Next_Group)
	       {
		  msg = "At end of article, press SPACE for next group.";
	       }
	  }
	else if (Slrn_Query_Next_Article)
	  msg = "At end of article, press SPACE for next unread article.";
	
	if (msg != NULL)
	  {
	     slrn_message (msg);
	     slrn_smg_refresh ();
	     ch = SLang_getkey ();
	  }
	
	if (ch == ' ')
	  {
	     At_End_Of_Article = NULL;
	     if (Slrn_Current_Header->next != NULL) art_next_unread ();
	     else skip_to_next_group ();
	  }
	else SLang_ungetkey (ch);
	return;
     }
   At_End_Of_Article = NULL;
   art_linedn_n (Article_Window_Height - 1);
}

static void goto_last_read (void)
{
   if (Last_Read_Header == NULL) return;
   slrn_goto_header (Last_Read_Header, 1);
}


static void art_prev_unread (void)
{
   if (prev_unread () && Article_Visible) art_pagedn  ();
}

static int next_unread (void)
{
   Slrn_Header_Type *h;
   int dn = 0;
   
   h = Slrn_Current_Header->next;
   
   while (h != NULL)
     {
	if ((h->flags & HEADER_HIDDEN) == 0)
	  {
	     /* if (0 == (h->flags & HEADER_READ)) break; */
	     dn++;
	  }
	if (0 == (h->flags & HEADER_READ)) break;
	h = h->next;
     }
   
   if (h == NULL)
     {
	slrn_message ("No following unread articles.");
	return 0;
     }
   
   Slrn_Current_Header = h;
   if (h->flags & HEADER_HIDDEN)
     {
	uncollapse_this_thread (h);
	find_header_line_number ();
     }
   else	Header_Number += dn;

   return 1;
}


static void art_next_unread (void)
{
   char ch;
   unsigned char ch1;
   
   if (next_unread ())
     {
	if (Article_Visible) art_pagedn  ();
	return;
     }
   
   if (Slrn_Query_Next_Group == 0)
     {
	skip_to_next_group ();
	return;
     }
   
   ch1 = SLang_Last_Key_Char;
   if (ch1 < 32) ch1 = 'n';
   slrn_message ("No following unread articles.  Press '%c' for other groups.", ch1);
   slrn_smg_refresh ();
   ch = SLang_getkey ();
   
   if ((unsigned char)ch == ch1)
     {
	skip_to_next_group ();
     }
   else SLang_ungetkey (ch);
}


static void next_high_score (void)
{
   Slrn_Header_Type *l;
   
   l = Slrn_Current_Header->next;
   
   while (l != NULL)
     {
	if (l->flags & HEADER_HIGH_SCORE)
	  {
	     break;
	  }
	l = l->next;
     }
   
   if (l == NULL)
     {
	slrn_error ("No more high scoring articles.");
	return;
     }
   
   if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
   
   Slrn_Current_Header = l;
   find_header_line_number ();
   
   if (Article_Visible)
     {
	art_pagedn ();
     }
}

/*{{{ next_header_same_subject */
static Slrn_Header_Type *Same_Subject_Start_Header;
static void next_header_same_subject (void)
{
   SLsearch_Type st;
   Slrn_Header_Type *l;
   char *prompt;
   static char same_subject[256];
   
   if ((Same_Subject_Start_Header == NULL)
       || (Slrn_Prefix_Arg_Ptr != NULL))
     {
	Slrn_Prefix_Arg_Ptr = NULL;
	prompt = "Subject: ";
	if (slrn_read_input (prompt, same_subject, 0) <= 0) return;
	Same_Subject_Start_Header = Slrn_Current_Header;
     }
   SLsearch_init (same_subject, 1, 0, &st);
   
   l = Slrn_Current_Header->next;
   
   while (l != NULL)
     {
	if (
#if 0
	    /* Do we want to do this?? */
	    ((l->flags & HEADER_READ) == 0) &&
#endif
	    (l->subject != NULL)
	    && (NULL != SLsearch ((unsigned char *) l->subject,
				  (unsigned char *) l->subject + strlen (l->subject),
				  &st)))
	  break;
	
	l = l->next;
     }
   
   if (l == NULL)
     {
	slrn_error ("No more articles on that subject.");
	l = Same_Subject_Start_Header;
	Same_Subject_Start_Header = NULL;
     }
   
   if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
   Slrn_Current_Header = l;
   find_header_line_number ();
   if ((Same_Subject_Start_Header != NULL)
       && (Article_Visible))
     {
	art_pagedn ();
     }
}

/*}}}*/

/*{{{ goto_header_number */
static void goto_header_number (void)
{
   int diff, i, ich;

   i = 0;
   ich = SLang_Last_Key_Char;
   do
     {
	i = i * 10 + (ich - '0');
	if (10 * i > Largest_Header_Number)
	  {
	     ich = '\r';
	     break;
	  }
	slrn_message ("Goto Header: %d", i);
	slrn_smg_refresh ();
     }
   while ((ich = SLang_getkey ()), (ich <= '9') && (ich >= '0'));
   
   if (SLKeyBoard_Quit) return;
   
   if (ich != '\r')
     SLang_ungetkey (ich);
   
   diff = i - Last_Cursor_Row;
   if (diff > 0) slrn_header_down_n (diff, 0); else slrn_header_up_n (-diff, 0);
   Slrn_Message_Present = 0;
#if SLRN_HAS_SLANG
   SLang_run_hooks ("header_number_hook", NULL, NULL);
#endif
   Slrn_Full_Screen_Update = 1;
}

/*}}}*/


/*}}}*/

/*{{{ article save/decode functions */
static int save_article_as_unix_mail (Slrn_Header_Type *h, FILE *fp)
{
   char *from;
   time_t now;
   Slrn_Article_Line_Type *l;
   
   if (read_article (h, 1, 0) < 0) return -1;
   
   from = extract_header ("From: ", 6);
   if (from != NULL) from = parse_from (from);
   if ((from == NULL) || (*from == 0)) from = "nobody@nowhere";
   
   time (&now);
   fprintf (fp, "From %s %s", from, ctime(&now));
   
   l = Slrn_Article_Lines;
   while (l != NULL)
     {
	if ((*l->buf == 'F')
	    && !strncmp ("From", l->buf, 4)
	    && ((unsigned char)(l->buf[4]) <= ' '))
	  {
	     putc ('>', fp);
	  }
	
	fputs (l->buf, fp);
	putc ('\n', fp);
	l = l->next;
     }
   fputs ("\n\n", fp);
   return 0;
}


static char *save_article_to_file (char *defdir, char *input_string)
{
   char file[256];
   char name[256];
   int save_tagged = 0;
   int save_thread = 0;
   int save_simple;
   FILE *fp;
   
   if (Num_Tag_List.len)
     {
	save_tagged = slrn_get_yesno_cancel ("Save tagged articles");
	if (save_tagged < 0) return NULL;
     }
   else if ((Slrn_Current_Header->child != NULL)
	    && (Slrn_Current_Header->child->flags & HEADER_HIDDEN))
     {
	save_thread = slrn_get_yesno_cancel ("Save this thread");
	if (save_thread == -1) return NULL;
     }
   
   save_simple = !(save_tagged || save_thread);
   
   if (*Output_Filename == 0)
     {
	unsigned int defdir_len;
	if (defdir == NULL) defdir = "News";
	
	defdir_len = strlen (defdir);
	sprintf (name, "%s/%s", defdir, Slrn_Current_Group_Name);
#ifndef VMS
#ifndef __os2__
	/* Lowercase first letter and see if it exists. */
	name[defdir_len + 1] = LOWER_CASE(name[defdir_len + 1]);
#endif
#endif
	slrn_make_home_filename (name, file);
#ifndef VMS
#ifndef __os2__
	if (1 != slrn_file_exists (file))
	  {
	     /* Lowercase version does not exist so user uppercase form. */
	     name[defdir_len + 1] = UPPER_CASE(name[defdir_len + 1]);
	     slrn_make_home_filename (name, file);
	  }
#endif
#endif
     }
   else strcpy (file, Output_Filename);
   
   if (slrn_read_input (input_string, file, 1) <= 0)
     {
	slrn_error ("Aborted.");
	return NULL;
     }
   
   if (NULL == (fp = fopen (file, "a")))
     {
	slrn_error ("Unable to open %s", file);
	return NULL;
     }
   strcpy (Output_Filename, file);
   
   if (save_simple) save_article_as_unix_mail (Slrn_Current_Header, fp);
   else if (save_tagged)
     {
	unsigned int i;
	unsigned int num_saved = 0;

	for (i = 0; i < Num_Tag_List.len; i++)
	  {
	     if (-1 == save_article_as_unix_mail (Num_Tag_List.headers[i], fp))
	       {
		  slrn_smg_refresh ();
		  SLang_Error = 0;
		  (void) SLang_input_pending (5);   /* half second delay */
	       }
	     else num_saved++;
	  }
	if (num_saved == 0) return NULL;
     }
   else
     {
	Slrn_Header_Type *h = Slrn_Current_Header;
	unsigned int num_saved = 0;
	do
	  {
	     if (-1 == save_article_as_unix_mail (h, fp))
	       {
		  slrn_smg_refresh ();
		  SLang_Error = 0;
		  (void) SLang_input_pending (5);   /* half second delay */
	       }
	     else num_saved++;

	     h = h->next;
	  }
	while ((h != NULL) && (h->parent != NULL));
	if (num_saved == 0) return NULL;
     }
   slrn_fclose (fp);
   
   if (SLang_Error) return NULL;
   
   return Output_Filename;
}

static void save_article (void)
{
   (void) save_article_to_file (Slrn_Save_Directory, "Save to file (^G aborts): ");
}

#if SLRN_HAS_DECODE
static void decode_article (void)
{
   char *uu_dir;
   char *file;
   
   if (NULL == (uu_dir = Slrn_Decode_Directory))
     {
	uu_dir = Slrn_Save_Directory;
     }
   else *Output_Filename = 0;	       /* force it to use this directory */
   
   file = save_article_to_file(uu_dir, "Filename (^G aborts): ");
   
   if (file == NULL) return;
   
   if (1 == slrn_get_yesno (1, "decode %s", file))
     {
	slrn_set_suspension (1);
	(void) slrn_uudecode_file (file, NULL, 0);
	slrn_set_suspension (0);
	if (SLang_Error == 0)
	  {
	     if (1 == slrn_get_yesno (1, "Delete %s", file))
	       {
		  if (-1 == slrn_delete_file (file))
		    slrn_error ("Unable to delete %s", file);
	       }
	  }
     }
   /* Since we have a decode directory, do not bother saving this */
   if (NULL != Slrn_Decode_Directory)
     *Output_Filename = 0;
}
#endif  /* SLRN_HAS_DECODE */

/*}}}*/
/*{{{ pipe_article */

int slrn_pipe_article_to_cmd (char *cmd)
{
   int n = -1;
#if SLRN_HAS_PIPING
   FILE *fp;
   Slrn_Article_Line_Type *l;

   if (-1 == select_article (1))
     return -1;

   if (NULL == (fp = slrn_popen (cmd, "w")))
     {
	slrn_error ("Unable to open pipe to %s", cmd);
	return -1;
     }
   
   l = Slrn_Article_Lines;
   n = 0;
   while (l != NULL)
     {
	fputs (l->buf, fp);
	putc ('\n', fp);
	n++;
	l = l->next;
     }
   fputs ("\n\n", fp);
   slrn_pclose (fp);
#else
   slrn_error ("Piping not implemented on this system.");
#endif
   return n;
}


static void pipe_article (void)
{
#if SLRN_HAS_PIPING
   static char cmd[256];
   int n;
   
   *cmd = 0;
   if (slrn_read_input ("Pipe to command: ", cmd, 1) <= 0)
     {
	slrn_error ("Aborted.  Command name is required.");
	return;
     }
   
   if (-1 != (n = slrn_pipe_article_to_cmd (cmd)))
     slrn_message ("Piped %d lines to %s.", n, cmd);
#else
   slrn_error ("Piping not implemented on this system.");
#endif
}

/*}}}*/

/*{{{ sorting header functions */

static int subject_cmp (register unsigned char *sa, register unsigned char *sb)
{
   register unsigned char ch;
   
   /* skip past re: */
   sa = (unsigned char *) slrn_skip_whitespace ((char *) sa);
   ch = *sa;
   if (((ch | 0x20) == 'r') && ((*(sa + 1) | 0x20) == 'e')
       && (*(sa + 2) == ':'))
     {
	sa = (unsigned char *) slrn_skip_whitespace ((char *) sa + 3);
     }
   
   sb = (unsigned char *) slrn_skip_whitespace ((char *) sb);
   ch = *sb;
   if (((ch | 0x20) == 'r') && ((*(sb + 1) | 0x20) == 'e')
       && (*(sb + 2) == ':'))
     {
	sb = (unsigned char *) slrn_skip_whitespace ((char *) sb + 3);
     }
   
   return slrn_case_strcmp ((unsigned char *) sa, (unsigned char *) sb);
}

#if 1
static int header_subj_cmp (Slrn_Header_Type **ap, Slrn_Header_Type **bp)
{
   int cmp;
   Slrn_Header_Type *a = *ap, *b = *bp;
   int ahigh, bhigh;
   
   ahigh = (0 != (a->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
   bhigh = (0 != (b->flags & (HEADER_HIGH_SCORE | FAKE_HEADER_HIGH_SCORE)));
   
   if (ahigh == bhigh)
     {
	cmp = subject_cmp ((unsigned char *) (a->subject),
			   (unsigned char *) (b->subject));
	if (!cmp) return a->number - b->number;
     }
   else cmp = bhigh - ahigh;
   
   return cmp;
}
#else
static int header_subj_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b)
{
   int cmp = subject_cmp ((unsigned char *) (*a)->subject,
			  (unsigned char *) (*b)->subject);
   if (!cmp) return (*a)->number - (*b)->number;
   return cmp;
}
#endif

typedef int (*Header_Cmp_Func_Type)(Slrn_Header_Type **, Slrn_Header_Type **);

static void sort_by_function (Header_Cmp_Func_Type cmp_func)
{
   Slrn_Header_Type **header_list, *h;
   unsigned int i, nheaders;
   void (*qsort_fun) (char *, unsigned int,
		      unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
   
   /* This is a silly hack to make up for braindead compilers and the lack of
    * uniformity in prototypes for qsort.
    */
   qsort_fun = (void (*)(char *, unsigned int,
			 unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
     qsort;
   
   /* Count the number we need to sort. */
   nheaders = 0;
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if (h->parent == NULL) nheaders++;
	h = h->real_next;
     }
   if (nheaders < 2) return;
   
   if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nheaders + 1)))
     {
	slrn_error ("sort_headers(): memory allocation failure.");
	return;
     }
   
   h = Slrn_First_Header;
   nheaders = 0;
   while (h != NULL)
     {
	if (h->parent == NULL)
	  header_list[nheaders++] = h;
	h = h->real_next;
     }
   header_list[nheaders] = NULL;
   
   (*qsort_fun) ((char *) header_list, nheaders, sizeof (Slrn_Header_Type *), cmp_func);
   
   /* What to do now depends upon the current threading state. */
   
   if (Headers_Threaded == 0)
     {
	header_list[0]->next = header_list[1];
	header_list[0]->prev = NULL;
	
	for (i = 1; i < nheaders; i++)
	  {
	     h = header_list[i];
	     h->next = header_list[i + 1];
	     h->prev = header_list[i - 1];
	  }
     }
   else
     {
	slrn_collapse_threads (0);
	/* The headers are threaded so we simply have sorted parents.  Arrange
	 * those.
	 */
	h = NULL;
	for (i = 0; i <= nheaders; i++)
	  {
	     Slrn_Header_Type *h1 = header_list[i];
	     if (h != NULL)
	       {
		  h->sister = h1;
		  while (h->child != NULL)
		    {
		       h = h->child;
		       while (h->sister != NULL) h = h->sister;
		    }
		  h->next = h1;
	       }
	     if (h1 != NULL) h1->prev = h;
	     h = h1;
	  }
     }
   Headers = header_list[0];
   Num_Headers = nheaders;
   find_header_line_number ();
   Slrn_Full_Screen_Update = 1;
   SLFREE (header_list);
   
   if (Slrn_Threads_Visible)
     {
	slrn_uncollapse_threads (1);
     }
}

static void sort_by_subject (void)
{
   sort_by_function (header_subj_cmp);
}

#if SLRN_HAS_SORT_BY_SCORE
static int header_score_cmp (Slrn_Header_Type **a, Slrn_Header_Type **b)
{
   /* sort by *descending* score */
   int cmp = (*b)->thread_score - (*a)->thread_score;
   if (cmp == 0) 
     {
	if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
	  return header_subj_cmp (a, b);
	else 
	  return (*a)->number - (*b)->number;
     }
   return cmp;
}

static void sort_by_score (void)
{
   sort_by_function (header_score_cmp);
}
#endif


static void sort_by_server_number (void)
{
   Slrn_Header_Type *h;
   
   /* This is easy since the real_next, prev are already ordered. */
   h = Slrn_First_Header;
   Num_Headers = 0;
   while (h != NULL)
     {
	Slrn_Header_Type *next = h->real_next;
	Num_Headers++;
	h->next = h->real_next;
	h->prev = h->real_prev;
	h->num_children = 0;
	h->flags &= ~(HEADER_HIDDEN | ALL_THREAD_FLAGS);
	h->sister = h->parent = h->child = NULL;
	h = next;
     }
   /* Now find out where to put the Headers pointer */
   while (Headers->prev != NULL) Headers = Headers->prev;
   
   Headers_Threaded = 0;
   
   find_header_line_number ();
   Slrn_Full_Screen_Update = 1;
}

static void sort_by_threads (void)
{
   thread_headers ();
   sort_threads ();
   
   if (Slrn_Threads_Visible)
     {
	slrn_uncollapse_threads (1);
     }
   Slrn_Full_Screen_Update = 1;
   Headers_Threaded = 1;
}

#if 0
   

static void toggle_sort (void)
{
#ifdef SLRN_HAS_SORT_BY_SCORE
   char rsp = slrn_get_response ("tTsSvVNnCc", "Sorting method: T-hreads, S-ubject, score V-alue, N-one, C-ancel");
#else
   char rsp = slrn_get_response ("tTsSNnCc", "Sorting method: T-hreads, S-ubject, N-one, C-ancel");
#endif

   Slrn_Sorting_Mode &= (SORT_BY_THREADS|SORT_BY_SUBJECT);
   
   switch (rsp | 0x20)
     {
      case 'n':
	Slrn_Sorting_Mode = 0;
	sort_by_server_number ();
	break;
	
      case 't':
	Slrn_Sorting_Mode = SORT_BY_THREADS;
	sort_by_threads ();
	break;
	
      case 's':
	Slrn_Sorting_Mode |= SORT_BY_SUBJECT;
	sort_by_subject ();
	break;
	
#ifdef SLRN_HAS_SORT_BY_SCORE
      case 'v':
	Slrn_Sorting_Mode |= SORT_BY_SCORE;
	sort_by_score ();
	break;
#endif

      default:
	return;
     }
}
#else

static void toggle_sort (void)
{
   int rsp;
   
   rsp = slrn_sbox_sorting_method ();
   if (rsp != -1)
     {
	Slrn_Sorting_Mode = rsp;
	sort_by_sorting_mode ();
     }
}

#endif

static void sort_by_sorting_mode (void)
{
   if ((Slrn_Sorting_Mode & SORT_BY_THREADS) == 0)
     sort_by_server_number ();
   else
     sort_by_threads ();
   
   if (Slrn_Sorting_Mode & SORT_BY_SUBJECT)
     sort_by_subject ();

#if SLRN_HAS_SORT_BY_SCORE
   if (Slrn_Sorting_Mode & SORT_BY_SCORE)
     sort_by_score ();
#endif
}

/*}}}*/
/*{{{ Thread related functions */

void slrn_collapse_threads (int sync_now)
{
   Slrn_Header_Type *h = Slrn_First_Header;
   
   if ((h == NULL) 
       || (Threads_Collapsed == 1))
     return;
   
   Num_Headers = 0;
   
   while (h != NULL)
     {
	if (h->parent != NULL) h->flags |= HEADER_HIDDEN;
	else
	  {
	     h->flags &= ~HEADER_HIDDEN;
	     Num_Headers++;
	  }
	h = h->real_next;
     }
   
   if (Slrn_Current_Header->flags & HEADER_HIDDEN)
     {
	slrn_header_up_n (1, 0);
     }
   
   if (sync_now) find_header_line_number ();

   Slrn_Full_Screen_Update = 1;
   Threads_Collapsed = 1;
}

void slrn_uncollapse_threads (int sync_now)
{
   Slrn_Header_Type *h = Slrn_First_Header;
   
   if ((h == NULL) 
       || (0 == Threads_Collapsed))
     return;
   
   Num_Headers = 0;
   while (h != NULL)
     {
	h->flags &= ~HEADER_HIDDEN;
	h = h->real_next;
	Num_Headers++;
     }
   Slrn_Full_Screen_Update = 1;
   Threads_Collapsed = 0;
   if (sync_now) find_header_line_number ();
}

static void uncollapse_header (Slrn_Header_Type *h)
{
   h->flags &= ~HEADER_HIDDEN;
}

static void collapse_header (Slrn_Header_Type *h)
{
   h->flags |= HEADER_HIDDEN;
}

static void for_this_tree (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *))
{
   Slrn_Header_Type *child = h->child;
   while (child != NULL)
     {
	for_this_tree (child, f);
	child = child->sister;
     }
   (*f) (h);
   Slrn_Full_Screen_Update = 1;
}

static void for_this_family (Slrn_Header_Type *h, void (*f)(Slrn_Header_Type *))
{
   while (h != NULL)
     {
	for_this_tree (h, f);
	h = h->sister;
     }
}


/* Up to calling routine to adjust header line number if necessary. */
static void uncollapse_this_thread (Slrn_Header_Type *h)
{
   Slrn_Header_Type *child;
   
   if (Threads_Collapsed == 0) return;
   
   while (h->parent != NULL) h = h->parent;
   if ((child = h->child) == NULL) return;
   if (0 == (child->flags & HEADER_HIDDEN)) return;
   
   for_this_family (child, uncollapse_header);
   Num_Headers += h->num_children;
   
   Threads_Collapsed = -1;	       /* uncertain */
}

/* Up to calling routine to adjust header line number and make sure to move
 * current header out of thread
 */
static void collapse_this_thread (Slrn_Header_Type *h)
{
   Slrn_Header_Type *child;
   
   if (Threads_Collapsed == 1) return;
   
   while (h->parent != NULL) h = h->parent;
   
   if ((child = h->child) == NULL) return;
   if (child->flags & HEADER_HIDDEN) return;
   
   for_this_family (child, collapse_header);
   Num_Headers -= h->num_children;
   Threads_Collapsed = -1;	       /* uncertain */
}

static void toggle_collapse_threads (void)
{
   if (Slrn_Prefix_Arg_Ptr != NULL)
     {
	if (Threads_Collapsed == 1)
	  {
	     slrn_uncollapse_threads (0);
	  }
	else slrn_collapse_threads (0);
	Slrn_Prefix_Arg_Ptr = NULL;
     }
   else
     {
	Slrn_Header_Type *child;
	
	/* Determine whether or not the thread is already collapsed.  Easy
	 * check */
	if ((Slrn_Current_Header->parent != NULL)
	    || (((child = Slrn_Current_Header->child) != NULL)
		&& ((child->flags & HEADER_HIDDEN) == 0)))
	  collapse_this_thread (Slrn_Current_Header);
	else
	  uncollapse_this_thread (Slrn_Current_Header);
	
	if (Slrn_Current_Header->flags & HEADER_HIDDEN) slrn_header_up_n (1, 0);
     }
   find_header_line_number ();
}

static Slrn_Header_Type *sort_thread_node (Slrn_Header_Type *h, char *tree)
{
   Slrn_Header_Type *last = NULL;
   static int level;
   char vline_char;
   
   if (h == NULL) return NULL;
   
#ifdef __os2__
   vline_char = SLSMG_VLINE_CHAR;
#else
   vline_char = (SLtt_Has_Alt_Charset ? SLSMG_VLINE_CHAR : ' ');
#endif

   while (1)
     {
	last = h;
	
	if (h->child != NULL)
	  {
	     Slrn_Header_Type *child = h->child;
	     int tree_level;
	     int save_level = level;
	     
	     h->next = child;
	     child->prev = h;
	     
	     
	     if (level == 0)
	       {
		  if (h->flags & FAKE_CHILDREN)
		    {
		       if ((child->flags & FAKE_PARENT) == 0)
			 {
			    level = 1;
			 }
		    }
	       }
	     else if (h->flags & FAKE_PARENT)
	       {
		  if (h->sister != NULL) tree[0] = vline_char;
		  else tree[0] = ' ';
		  tree[1] = ' ';
		  level = 1;
	       }
	     
	     tree_level = 2 * level - 2;
	     
	     if (level && (tree_level < sizeof (h->tree) - 2))
	       {
		  if (h->sister != NULL)
		    {
		       if (((h->sister->flags & FAKE_PARENT) == 0)
			   || (h->flags & FAKE_PARENT))
			 {
			    tree[tree_level] = vline_char;
			 }
		       else tree[tree_level] = ' ';
		    }
		  else
		    {
		       if ((h->parent == NULL) && (h->flags & FAKE_CHILDREN))
			 {
			    tree[tree_level] = vline_char;
			 }
		       else tree[tree_level] = ' ';
		    }
		  tree[tree_level + 1] = ' ';
		  tree[tree_level + 2] = 0;
	       }
	     
	     level++;
	     last = sort_thread_node (h->child, tree);
	     level--;
	     
	     if (level &&
		 ((tree_level < sizeof (h->tree) - 2)))
	       tree[tree_level] = 0;
	     
	     level = save_level;
	  }
	
	if (h->flags & FAKE_PARENT) *tree = 0;
	
	if (*tree)
	  {
	     strncpy ((char *) h->tree, tree, sizeof (h->tree) - 1);
	     h->tree[sizeof (h->tree) - 1] = 0;
	  }
	h = h->sister;
	last->next = h;
	if (h == NULL) break;
	h->prev = last;
     }
   return last;
}


static unsigned int compute_num_children (Slrn_Header_Type *h)
{
   unsigned int n = 0, dn;
   
   h = h->child;
   while (h != NULL)
     {
	n++;
	if (h->child == NULL) dn = 0;
	else
	  {
	     dn = compute_num_children (h);
	     n += dn;
	  }
	h->num_children = dn;
	h = h->sister;
     }
   return n;
}

static void sort_threads (void)
{
   Slrn_Header_Type *h;
   char tree[MAX_TREE_SIZE];
   
   h = Slrn_First_Header;
   Headers = NULL;
   
   if (h == NULL) return;
   while (h != NULL)
     {
	if ((h->parent == NULL) && (Headers == NULL))
	  Headers = h;
	
	h->prev = h->next = NULL;
	h->flags &= ~HEADER_HIDDEN;
	h = h->real_next;
     }
   Threads_Collapsed = 0;
   
   if (Headers == NULL)
     slrn_exit_error ("Internal Error.");
   
   *tree = 0;
   sort_thread_node (Headers, tree);
   while (Headers->prev != NULL) Headers = Headers->prev;
   
   slrn_collapse_threads (0);
   h = Headers;
   while (h != NULL)
     {
	if (h->child == NULL) h->num_children = 0;
	else
	  {
	     Slrn_Header_Type *next;
	     h->num_children = compute_num_children (h);
	     next = h->next;
	     while ((next != NULL) && (next->parent != NULL))
	       {
#if SLRN_HAS_SORT_BY_SCORE
		  if (next->flags & HEADER_HIGH_SCORE)
		    h->flags |= FAKE_HEADER_HIGH_SCORE;
		  if (next->score > h->thread_score)
		    h->thread_score = next->score;
#else
		  if (next->flags & HEADER_HIGH_SCORE)
		    {
		       h->flags |= FAKE_HEADER_HIGH_SCORE;
		       break;
		    }
#endif
		  next = next->next;
	       }
	  }
	h = h->sister;
     }
   find_header_line_number ();
}

static void link_same_subjects (void)
{
   Slrn_Header_Type **header_list, *h;
   unsigned int i, nparents;
   void (*qsort_fun) (char *, unsigned int, unsigned int, int (*)(Slrn_Header_Type **, Slrn_Header_Type **));
   
   /* This is a silly hack to make up for braindead compilers and the lack of
    * uniformity in prototypes for qsort.
    */
   qsort_fun = (void (*)(char *,
			 unsigned int, unsigned int,
			 int (*)(Slrn_Header_Type **, Slrn_Header_Type **)))
     qsort;
   
   h = Slrn_First_Header;
   nparents = 0;
   while (h != NULL)
     {
	if (h->parent == NULL)
	  nparents++;
	h = h->real_next;
     }
   if (nparents < 2) return;
   
   
   if (NULL == (header_list = (Slrn_Header_Type **) SLCALLOC (sizeof (Slrn_Header_Type *), nparents)))
     {
	slrn_error ("link_same_subjects: memory allocation failure.");
	return;
     }
   
   h = Slrn_First_Header;
   i = 0;
   while (i < nparents)
     {
	if (h->parent == NULL) header_list[i++] = h;
	h = h->real_next;
     }
   
   (*qsort_fun) ((char *) header_list,
		 nparents, sizeof (Slrn_Header_Type *), header_subj_cmp);
   
   h = header_list[0];
   for (i = 1; i < nparents; i++)
     {
	Slrn_Header_Type *h1 = header_list[i];
	if (0 == subject_cmp ((unsigned char *) h->subject,
			      (unsigned char *) h1->subject))
	  {
	     if (h->child == NULL)
	       {
		  h->child = h1;
	       }
	     else
	       {
		  Slrn_Header_Type *child = h->child;
		  while (child->sister != NULL) child = child->sister;
		  child->sister = h1;
	       }
	     
	     h1->parent = h;
	     h->flags |= FAKE_CHILDREN;
	     h1->flags |= FAKE_PARENT;
	     if (h1->flags & FAKE_CHILDREN)
	       {
		  /* Well, we have to link them up to the new parent.  That
		   * is, h1 will become their sister.  So, extract the
		   * adopted children of h1 and make them the sister,
		   */
		  Slrn_Header_Type *child = h1->child, *last_child;
		  last_child = child;
		  
		  /* child CANNOT be NULL here!! (the parent claims to have
						  children) */
		  child = child->sister;
		  while (child != NULL)
		    {
		       if (child->flags & FAKE_PARENT)
			 break;
		       last_child = child;
		       child = child->sister;
		    }
		  
		  if (last_child->flags & FAKE_PARENT)
		    {
		       child = last_child;
		       h1->child = NULL;
		    }
		  else last_child->sister = NULL;
		  
		  last_child = child;
		  while (child != NULL)
		    {
		       child->parent = h;
		       /* No need to set fake parent flags since fake children
			* are all group together.  That is, once you loop
			* through the sisters and find one, you have found them
			* all.
			*/
		       child = child->sister;
		    }
		  
		  /* Now h1 will become the sister. */
		  child = h1;
		  while (child->sister != NULL) child = child->sister;
		  child->sister = last_child;
		  h1->flags &= ~FAKE_CHILDREN;
	       }
	  }
	else h = h1;
     }
   SLFREE (header_list);
}

typedef struct
{
   unsigned long ref_hash;
   Slrn_Header_Type *h;
}
Relative_Type;


static void link_lost_relatives (void)
{
   unsigned int n, i, j;
   Slrn_Header_Type *h;
   Relative_Type *relatives;
   
   /* count the number of possible relatives */
   n = 0;
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if ((h->parent == NULL)
	    && (h->refs != NULL)
	    && (*h->refs != 0)) n++;
	
	h = h->real_next;
     }
   if (n < 2) return;
   if (NULL == (relatives = (Relative_Type *) SLMALLOC (sizeof (Relative_Type) * n)))
     return;
   
   n = 0;
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if ((h->parent == NULL)
	    && (h->refs != NULL)
	    && (*h->refs != 0))
	  {
	     unsigned char *r, *ref_begin;
	     
	     r = (unsigned char *) h->refs;
	     while (*r && (*r != '<')) r++;
	     if (*r == '<')
	       {
		  ref_begin = r;
		  while (*r && (*r != '>')) r++;
		  if (*r == '>') r++;
		  relatives[n].ref_hash = slrn_compute_hash (ref_begin, r);
		  relatives[n].h = h;
		  n++;
	       }
	  }
	h = h->real_next;
     }
   
   for (i = 0; i < n; i++)
     {
	unsigned long ref_hash;
	Relative_Type *ri = relatives + i;
	Slrn_Header_Type *rih;
	
	ref_hash = ri->ref_hash;
	rih = ri->h;
	
	for (j = i + 1; j < n; j++)
	  {
	     if (relatives[j].ref_hash == ref_hash)
	       {
		  Slrn_Header_Type *rjh = relatives[j].h;
		  if (rih->parent != NULL)
		    {
		       rih->sister = rjh;
		       rjh->parent = rih->parent;
		    }
		  else if (rih->child == NULL)
		    {
		       rih->child = rjh;
		       rjh->parent = rih;
		       rih->flags |= FAKE_CHILDREN;
		    }
		  else
		    {
		       Slrn_Header_Type *child = rih->child;
		       /* This is an important step.  All adopted children
			* get linked to the LAST child.  This ordering
			* assumption is used elsewhere.
			*/
		       while (child->sister != NULL) child = child->sister;
		       child->sister = rjh;
		       rjh->parent = rih;
		       rih->flags |= FAKE_CHILDREN;
		    }
		  rjh->flags |= FAKE_PARENT;
		  break;
	       }
	  }
     }
   SLFREE (relatives);
}

static void thread_headers (void)
{
   Slrn_Header_Type *h, *ref;
   char *r0, *r1, *rmin;
   
   make_hash_table ();
   
   h = Slrn_First_Header;
   while (h != NULL)
     {
	h->next = h->prev = h->child = h->parent = h->sister = NULL;
	h->flags &= ~ALL_THREAD_FLAGS;
	*h->tree = 0;
	h = h->real_next;
     }
   
   /* SLMEMSET ((char *) Lost_Ancestors, 0, sizeof (Lost_Ancestors)); */
   
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if (*h->refs == 0)
	  {
	     h = h->real_next;
	     continue;
	  }
	
	rmin = h->refs;
	r1 = rmin + strlen (rmin);
	
	while (1)
	  {
	     while ((r1 > rmin) && (*r1 != '>')) r1--;
	     r0 = r1 - 1;
	     while ((r0 >= rmin) && (*r0 != '<')) r0--;
	     if ((r0 < rmin) || (r1 == rmin)) break;
	     
	     ref = find_header_from_msgid (r0, r1 + 1);
	     if (ref != NULL)
	       {
		  Slrn_Header_Type *child, *rparent;
		  rparent = ref;
		  while (rparent->parent != NULL) rparent = rparent->parent;
		  if (rparent == h)
		    {
		       /* self referencing!!! */
		       slrn_error ("Article %d is part of reference loop!", h->number);
		    }
		  else
		    {
		       h->parent = ref;
		       child = ref->child;
		       if (child == NULL) ref->child = h;
		       else
			 {
			    while (child->sister != NULL) child = child->sister;
			    child->sister = h;
			 }
		       break;
		    }
	       }
	     r1 = r0;
	  }
	h = h->real_next;
     }
   
   /* No perform a re-arrangement such that those with the no parents but
    * share the same reference are placed side-by-side as sisters.
    */
   
   link_lost_relatives ();
   
   /* Now perform sort on subject to catch those that have fallen through the
    * cracks, i.e., no references */
   link_same_subjects ();
   
   /* Now link others up as sisters */
   h = Slrn_First_Header;
   while ((h != NULL) && (h->parent != NULL))
     {
	h = h->real_next;
     }
   
   while (h != NULL)
     {
	Slrn_Header_Type *next;
	next = h->real_next;
	while ((next != NULL) && (next->parent != NULL))
	  next = next->real_next;
	h->sister = next;
	h = next;
     }
}

/*}}}*/

/*{{{ select_article */

/* returns 0 if article selected, -1 if something went wrong or 1 if article
 * already selected.
 */
static int select_article (int do_mime)
{
   int ret = 1;
   
   Slrn_Full_Screen_Update = 1;
   
   uncollapse_this_thread (Slrn_Current_Header);
   
   if (Slrn_Current_Header != Header_Showing)
     {
	if (read_article (Slrn_Current_Header, 1, 1) < 0) return -1;
#if SLRN_HAS_MIME
	if (do_mime && Slrn_Use_Mime && Slrn_Mime_Needs_Metamail)
	  {
	     if (slrn_mime_call_metamail ())
	       return -1;
	  }
#endif
	ret = 0;
     }
   Header_Window_Height = SLtt_Screen_Rows - Article_Window_Height - 4;
   if (Header_Window_Height < 1)
     art_winch_sig ();
   Article_Visible = 1;
   return ret;
}

/*}}}*/

/*{{{ mark_spot and exchange_mark */

static void mark_spot (void)
{
   Mark_Header = Slrn_Current_Header;
   slrn_message ("Mark set.");
}


static void exchange_mark (void)
{
   if (Mark_Header == NULL)
     {
	slrn_error ("Mark not set.");
	return;
     }
   
   if (-1 == slrn_goto_header (Mark_Header, 0)) return;
   mark_spot ();
}

/*}}}*/
/*{{{ subject/author header searching commands */

static void header_generic_search (int dir, int type)
{
   static char search_str[256];
   SLsearch_Type st;
   Slrn_Header_Type *l;
   char prompt[80];
   
   sprintf (prompt, "%s Search %s: ",
	    type == 's' ? "Subject" : "Author",
	    dir > 0 ? "Forward" : "Backward");
   
   if (slrn_read_input (prompt, search_str, 0) <= 0)
     {
	return;
     }
   
   SLsearch_init (search_str, 1, 0, &st);
   
   if (dir > 0) l = Slrn_Current_Header->next;
   else l = Slrn_Current_Header->prev;
   
   while (l != NULL)
     {
	if (type == 's')
	  {
	     if ((l->subject != NULL)
		 && (NULL != SLsearch ((unsigned char *) l->subject,
				       (unsigned char *) l->subject + strlen (l->subject),
				       &st)))
	       break;
	  }
	else if ((l->subject != NULL)
		 && (NULL != SLsearch ((unsigned char *) l->from,
				       (unsigned char *) l->from + strlen (l->from),
				       &st)))
	  break;
	
	if (dir > 0) l = l->next; else l = l->prev;
     }
   
   if (l == NULL)
     {
	slrn_error ("Not found.");
	return;
     }
   
   if (l->flags & HEADER_HIDDEN) uncollapse_this_thread (l);
   Slrn_Current_Header = l;
   find_header_line_number ();
}

static void subject_search_forward (void)
{
   header_generic_search (1, 's');
}
static void subject_search_backward (void)
{
   header_generic_search (-1, 's');
}

static void author_search_forward (void)
{
   header_generic_search (1, 'a');
}
static void author_search_backward (void)
{
   header_generic_search (-1, 'a');
}

/*}}}*/

/*{{{ score header support */

/*{{{ kill list functions */


typedef struct Kill_List_Type
{
#define MAX_DKILLS 50
   int nums[MAX_DKILLS];
   unsigned int num_used;
   struct Kill_List_Type *next;
}
Kill_List_Type;

static Kill_List_Type *Kill_List;
static Kill_List_Type *Missing_Article_List;

static Kill_List_Type *add_to_specified_kill_list (int num, Kill_List_Type *root)
{
   if (num < 0) return root;
   
   if ((root == NULL) || (root->num_used == MAX_DKILLS))
     {
	Kill_List_Type *k;
	k = (Kill_List_Type *) SLMALLOC (sizeof (Kill_List_Type));
	if (k == NULL) return root;
	k->num_used = 0;
	k->next = root;
	root = k;
     }
   root->nums[root->num_used++] = num;
   return root;
}

static void add_to_kill_list (int num)
{
   Kill_List = add_to_specified_kill_list (num, Kill_List);
   Number_Killed++;
}

static void add_to_missing_article_list (int num)
{
   Missing_Article_List = add_to_specified_kill_list (num, Missing_Article_List);
}

static void free_specific_kill_list_and_update (Kill_List_Type *k)
{
   while (k != NULL)
     {
	Kill_List_Type *next = k->next;
	unsigned int i, imax = k->num_used;
	int *nums = k->nums;
	for (i = 0; i < imax; i++)
	  {
	     slrn_mark_article_as_read (NULL, nums[i]);
	  }
	SLFREE (k);
	k = next;
     }
}

static void free_kill_lists_and_update (void)
{
   free_specific_kill_list_and_update (Kill_List);
   Kill_List = NULL;
   Number_Killed = 0;
   free_specific_kill_list_and_update (Missing_Article_List);
   Missing_Article_List = NULL;
}


/*}}}*/

/*{{{ apply_score */
static Slrn_Header_Type *apply_score (Slrn_Header_Type *h)
{
   int score;
   
   if (h == NULL) return h;
   if (Slrn_Apply_Score && Perform_Scoring)
     score = slrn_score_header (h, Slrn_Current_Group_Name);
   else score = 0;
   
   if (score)
     {
	if (score > 0)
	  {
	     h->flags |= HEADER_HIGH_SCORE;
	     Number_High_Scored++;
	  }
	else if (score < 0)
	  {
	     if (score == -9999)
	       {
		  int number = h->number;
		  SLFREE (h->subject);
		  SLFREE (h);
		  add_to_kill_list (number);
		  return NULL;
	       }
	     else h->flags |= (HEADER_READ | HEADER_LOW_SCORE);
	     Number_Low_Scored++;
	     /* The next line should be made configurable */
	     kill_cross_references (h);
	  }
     }
   h->thread_score = h->score = score;
   return h;
}

/*}}}*/

/*{{{ score_headers */
static void score_headers (void)
{
   Slrn_Header_Type *h = Slrn_First_Header;
   int percent, last_percent, delta_percent;
   int total;
   
   if ((h == NULL) || (Slrn_Apply_Score == 0)) return;
   
   slrn_set_suspension (1);
   
   total = Num_Headers;
   
   percent = Num_Headers = 0;
   delta_percent = (30 * 100) / total + 1;
   last_percent = -delta_percent;
   
   while (h != NULL)
     {
	Slrn_Header_Type *prev, *next;
	prev = h->real_prev;
	next = h->real_next;
	percent = (100 * Num_Headers) / total;
	if (percent >= last_percent + delta_percent)
	  {
	     slrn_message ("Scoring articles: %2d%%, Killed: %u, High: %u, Low: %u",
			   percent, Number_Killed, Number_High_Scored, Number_Low_Scored);
	     slrn_smg_refresh ();
	     last_percent = percent;
	  }
	
	h = apply_score (h);
	if (h == NULL)
	  {
	     if (prev == NULL)
	       {
		  Slrn_First_Header = next;
	       }
	     else
	       {
		  prev->next = prev->real_next = next;
	       }
	     if (next != NULL)
	       {
		  next->prev = next->real_prev = prev;
	       }
	  }
	else
	  {
	     Num_Headers++;
	  }
	h = next;
     }
   Header_Number = 1;
   Slrn_Current_Header = Headers = Slrn_First_Header;
   slrn_set_suspension (0);
}

/*}}}*/

static void create_score (void)
{
   (void) slrn_edit_score (Slrn_Current_Header, Slrn_Current_Group_Name);
}


/*}}}*/

/*{{{ get headers from server and process_xover_line */

/*{{{ process_xover_line */

/* The line consists of:
 * id|subj|from|date|msgid|refs|bytes|line|misc stuff
 * Here '|' is a TAB.  The following code parses this.
 */
static Slrn_Header_Type *process_xover_line (char *buf)
{
   Slrn_Header_Type *h;
   unsigned int len;
   char *subj, *bmax, *b;
   
   /* Find the end of it */
   b = buf;
   /* skip past id */
   while (*b && (*b != '\t')) b++;
   *b++ = 0;
   subj = b;
   
   len = strlen (b);
   
   /* Unfortunately, some servers are defective.  So, we add enough
    * space (13) to avoid problems.  I found this out the hard way.
    */
   
   if ((NULL == (h = (Slrn_Header_Type *) SLMALLOC (sizeof(Slrn_Header_Type))))
       || (NULL == (b = (char *) SLMALLOC (len + 13))))
     {
	slrn_exit_error ("Memory allocation error.");
     }
   
   SLMEMSET ((char *) h, 0, sizeof (Slrn_Header_Type));
   SLMEMSET (b + len, 0, 13);
   
   h->subject = b;
   strcpy (b, subj);
   
   h->number = atoi(buf);
   
   /* now rest of headers */
   bmax = b + len;
   
   while ((b < bmax) && (*b != '\t')) b++;
   *b++ = 0;
   
   h->from = b;
   while ((b < bmax) && (*b != '\t')) b++;
   *b++ = 0;
   
   h->date = b;
   while ((b < bmax) && (*b != '\t')) b++;
   *b++ = 0;
   
   h->msgid = b;
   while ((b < bmax) && (*b != '\t')) b++;
   h->hash = slrn_compute_hash ((unsigned char *) h->msgid,
				(unsigned char *) b);
   *b++ = 0;
   
   h->refs = b;
   while ((b < bmax) && (*b != '\t')) b++;
   *b++ = 0;
   
   /* look for xrefs */
   /* skip bytes */
   
   while ((b < bmax) && (*b != '\t')) b++;
   *b++ = 0;		       /* skipped bytes */
   
   /* get number of lines */
   h->lines = atoi (b);
   while ((b < bmax) && *b && (*b != '\t')) b++;
   
   /* Now at misc stuff */
   h->xref = NULL;
   if (*b)
     {
	*b++ = 0;
	/* Optional fields consist of Header: stuff.  Look for Xref: ... */
	while (b < bmax)
	  {
	     char *xb = b;
	     
	     /* skip to next field. */
	     while ((b < bmax) && (*b != '\t')) b++;
	     if (b < bmax) *b++ = 0;
	     
	     if (0 == slrn_case_strncmp ((unsigned char *) xb, 
					 (unsigned char *) "Xref: ", 6))
	       {
		  h->xref = xb + 6;
		  break;
	       }
	  }
     }
   
   if ((Slrn_Score_After_XOver == 0) && Perform_Scoring)
     {
	if (NULL == (h = apply_score (h)))
	  return h;
     }
   
#if SLRN_HAS_MIME
   if (Slrn_Use_Mime)
     {
	slrn_rfc1522_decode_string (h->subject);
	slrn_rfc1522_decode_string (h->from);
     }
#endif
#if SLRN_HAS_GROUPLENS
   if (Slrn_Use_Group_Lens)
     {
	h->gl_rating = h->gl_pred = -1;
     }
#endif
   return h;
}

/*}}}*/

/*{{{ get_headers from server */
static int get_headers (int min, int max, int *totalp)
{
   char buf[NNTP_BUFFER_SIZE];
   Slrn_Header_Type *h;
   /* int percent, last_percent, dpercent, */
   int expected_num;
   int scoring;
   int total = *totalp;
   int reads_per_update;
   
   if ((total == 0) || (SLang_Error == USER_BREAK))
     return -1;
   
   scoring = Perform_Scoring && !Slrn_Score_After_XOver;

#if 0
   if (Slrn_Delta_Percent <= 0)
     {
	if (scoring)
	  {
	     dpercent = (50 * 20) / total + 1;
	  }
	else dpercent = (50 * 100) / total + 1;
     }
   else dpercent = Slrn_Delta_Percent;
   last_percent = -dpercent;
#endif

   if ((reads_per_update = Slrn_Reads_Per_Update) < 5)
     reads_per_update = 50;

   if (scoring) reads_per_update = reads_per_update / 3 + 1;
   
   Num_Headers = Header_Number;
   
   slrn_set_suspension (1);
   
   Slrn_Server_Obj->sv_open_xover (min, max);

   expected_num = min;
   while (Slrn_Server_Obj->sv_read_xover(buf, sizeof(buf) - 10) != NULL)
     {
	int num = Header_Number + Number_Killed;
	int this_num;
	
	if (SLang_Error == USER_BREAK)
	  {
	     slrn_set_suspension (0);
	     return -1;
	  }

	this_num = atoi(buf);
	
	if (expected_num != this_num)
	  {
	     int bad_num;
	     
	     total -= (this_num - expected_num);
	     
	     for (bad_num = expected_num; bad_num < this_num; bad_num++)
	       add_to_missing_article_list (bad_num);
	  }
	
/*	if (total <= 0) percent = 100;
 *	 else percent = (num * 100) / total;
 */
	expected_num = this_num + 1;
	
	h = process_xover_line (buf);
	
	/* if (percent >= last_percent + dpercent) */
	num++;
	if (1 == (num % reads_per_update))
	  {
	     if (!SLang_Error)
	       {
		  if (scoring)
		    {
/*		       slrn_message ("Headers Received and Scored: %2d%%, Number Killed: %u, Number Scoring High: %u",
 *				      percent, Number_Killed, Number_High_Scored);
 */		       slrn_message ("Headers Received and Scored: %3d/%-3d, Killed: %u, High: %u, Low: %u",
				     num, total, 
				     Number_Killed, Number_High_Scored, Number_Low_Scored);
		    }
		  else
		    slrn_message ("Headers Received: %2d/%d", num, total);
		    /* slrn_message ("Headers Received: %d%%", percent); */
		  
		  slrn_smg_refresh ();
	       }
	     /* last_percent = percent; */
	  }
	
	if (h == NULL) continue;
	
	if (Slrn_First_Header == NULL)
	  {
	     Slrn_First_Header = Headers = h;
	  }
	else
	  {
	     h->real_next = Slrn_Current_Header->real_next;
	     h->real_prev = Slrn_Current_Header;
	     Slrn_Current_Header->real_next = h;
	     
	     if (h->real_next != NULL)
	       {
		  h->real_next->real_prev = h;
	       }
	  }
	
	Slrn_Current_Header = h;
	Header_Number++;
     }
   Slrn_Server_Obj->sv_close_xover ();
   
   if (expected_num != max + 1)
     {
	int bad_num;
	     
	total -= (max - expected_num) + 1;
	     
	for (bad_num = expected_num; bad_num <= max; bad_num++)
	  add_to_missing_article_list (bad_num);
     }
   
   slrn_set_suspension (0);
   *totalp = total;
   
   if (Num_Headers == Header_Number) return -1;
   Num_Headers = Header_Number;
   
   return 0;
}


/*}}}*/


/*}}}*/
/*{{{ get parent/children headers, etc... */

static void insert_header (Slrn_Header_Type *ref)
{
   int n, id;
   Slrn_Header_Type *h;
   Slrn_Range_Type *r;
   
   ref->hash_next = Header_Table[ref->hash % HEADER_TABLE_SIZE];
   Header_Table[ref->hash % HEADER_TABLE_SIZE] = ref;
   
   n = ref->number;
   h = Slrn_First_Header;
   while (h != NULL)
     {
	if (h->number >= n)
	  {
	     ref->real_next = h;
	     ref->real_prev = h->real_prev;
	     if (h->real_prev != NULL) h->real_prev->real_next = ref;
	     h->real_prev = ref;
	     
	     if (h == Slrn_First_Header) Slrn_First_Header = ref;
	     if (h == Headers) Headers = ref;
	     
	     break;
	  }
	h = h->real_next;
     }
   
   if (h == NULL)
     {
	h = Slrn_First_Header;
	while (h->real_next != NULL) h = h->real_next;
	
	ref->real_next = NULL;
	ref->real_prev = h;
	h->real_next = ref;
     }
   
   /* This is ok since this header will not be marked as hidden. */
   Num_Headers++;
   if ((id = ref->number) <= 0) return;
   
   /* Set the flags for this guy. */
   r = Current_Group->range.next;
   while (r != NULL)
     {
	if (r->min > id) break;
	if (r->max >= id)
	  {
	     ref->flags = HEADER_READ;
	     return;
	  }
	r = r->next;
     }
   ref->flags &= ~HEADER_READ;
}


static int get_header_by_message_id (char *msgid, int no_error_no_thread)
{
   char buf[NNTP_BUFFER_SIZE];
   Slrn_Header_Type *ref;
   
   if ((msgid == NULL) || (*msgid == 0)) return -1;
   
   ref = find_header_from_msgid (msgid, msgid + strlen (msgid));
   if (ref != NULL)
     {
	Slrn_Current_Header = ref;
	if (no_error_no_thread == 0)
	  find_header_line_number ();
	Slrn_Full_Screen_Update = 1;
	return 0;
     }
   
   slrn_message ("Finding parent from server...");
   slrn_smg_refresh ();
   
   slrn_set_suspension (1);
   /* Try reading it from the server */
   if (NULL == Slrn_Server_Obj->sv_head_from_msgid (msgid, buf, sizeof (buf)))
     {
	slrn_set_suspension (0);
	if (no_error_no_thread == 0)
	  {
	     slrn_error ("Article %s not available.", msgid);
	     return -1;
	  }
	return 1;
     }
   
   Slrn_Message_Present = 0;
   ref = process_xover_line (buf);
#if 0
   if (Slrn_Score_After_XOver)
     ref = apply_score (ref);
#endif

   slrn_set_suspension (0);

   if (ref == NULL) return -1;
   
   get_header_real_name (ref);
   
   insert_header (ref);
   
   Slrn_Current_Header = ref;
   if (no_error_no_thread == 0)
     {
	sort_by_sorting_mode ();
     }
   return 0;
}

/* returns -1 if not implemented or the number of children returned from
 * the server.
 */
static int find_children_headers (Slrn_Header_Type *parent)
{
   char buf[NNTP_BUFFER_SIZE];
   int id_array[1000];
   int num_ids, i, id;
   
   if (-1 == Slrn_Server_Obj->sv_xpat_cmd ("References",
			    Slrn_Server_Min, Slrn_Server_Max,
			    parent->msgid))
     {
	slrn_error ("Your server does not provide support for this feature.");
	return -1;
     }
   
   num_ids = 0;
   while (Slrn_Server_Obj->sv_read_line (buf, sizeof (buf) - 1) != NULL)
     {
	id = atoi (buf);
	if (id <= 0) continue;
	
	if (NULL != find_header_from_serverid (id)) continue;
	id_array[num_ids] = id;
	num_ids++;
     }
   
   for (i = 0; i < num_ids; i++)
     {
	id = id_array[i];
	Slrn_Server_Obj->sv_open_xover (id, id);
	
	/* This will loop once. */
	while (NULL != Slrn_Server_Obj->sv_read_xover(buf, sizeof(buf) - 10))
	  {
	     Slrn_Header_Type *h;
	     h = process_xover_line (buf);
	     if (h == NULL) continue;
	     get_header_real_name (h);
	     insert_header (h);
	  }
	Slrn_Server_Obj->sv_close_xover ();
     }
   return num_ids;
}

static void get_children_headers_1 (Slrn_Header_Type *h)
{
   while (h != NULL)
     {
	(void) find_children_headers (h);
	if (h->child != NULL)
	  {
	     get_children_headers_1 (h->child);
	  }
	h = h->sister;
     }
}

static void get_children_headers (void)
{
   Slrn_Header_Type *h;
   
   slrn_message ("Finding children from server...");
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
   
   if (find_children_headers (Slrn_Current_Header) < 0) return;
   sort_by_sorting_mode ();
   
   h = Slrn_Current_Header->child;
   if (h != NULL) 
     {
	/* Now walk the tree getting children headers.  For efficiency,
	 * only children currently threaded will be searched.  Hopefully the
	 * above attempt got everything.  If other newsreaders did not chop off
	 * headers, this would be unnecessary!
	 */
	get_children_headers_1 (h);
	sort_by_sorting_mode ();
     }
   uncollapse_this_thread (Slrn_Current_Header);
   find_header_line_number ();
}

static void get_parent_header (void)
{
   char *r1, *r0, *rmin;
   unsigned int len;
   char buf[512];
   int no_error_no_thread;
   Slrn_Header_Type *last_header;
   
   if (Slrn_Current_Header == NULL) return;
   
   if (Slrn_Prefix_Arg_Ptr == NULL) no_error_no_thread = 0;
   else no_error_no_thread = 1;
   
   last_header = NULL;
   r1 = rmin = NULL;
   do
     {
	rmin = Slrn_Current_Header->refs;
	if (rmin == NULL) break;
	
	if (last_header != Slrn_Current_Header)
	  {
	     last_header = Slrn_Current_Header;
	     r1 = rmin + strlen (rmin);
	  }
	
	while ((r1 > rmin) && (*r1 != '>')) r1--;
	r0 = r1 - 1;
	while ((r0 >= rmin) && (*r0 != '<')) r0--;
	
	if ((r0 < rmin) || (r1 == rmin))
	  {
	     if (no_error_no_thread) break;
	     slrn_error ("Article has no parent reference.");
	     return;
	  }
	
	len = (unsigned int) ((r1 + 1) - r0);
	strncpy (buf, r0, len);
	buf[len] = 0;
	r1 = r0;
     }
   while ((get_header_by_message_id (buf, no_error_no_thread) >= 0)
	  && no_error_no_thread);
   
   if (no_error_no_thread)
     {
	sort_by_sorting_mode ();
	get_children_headers ();
     }
   uncollapse_this_thread (Slrn_Current_Header);
}

static void locate_header_by_msgid (void)
{
   char msgid[256];
   *msgid = 0;
   if (slrn_read_input ("Enter Message-Id:", msgid, 1) <= 0) return;
   get_header_by_message_id (msgid, 0);
   uncollapse_this_thread (Slrn_Current_Header);
}


/*}}}*/

/*{{{ article window display modes */

static void hide_article (void)
{
   Slrn_Full_Screen_Update = 1;
   if (Article_Visible == 0)
     {
	select_article (1);
	return;
     }
   
   Article_Visible = 0;
   Top_Line = NULL;
   Header_Window_Height = SLtt_Screen_Rows - 3;
   HScroll = 0;
}


static void art_left (void)
{
   if (HScroll == 0) return;
   HScroll -= (SLtt_Screen_Cols * 2) / 3;
   if (HScroll < 0) HScroll = 0;
   Slrn_Full_Screen_Update = 1;
}

static void art_right (void)
{
   HScroll += (SLtt_Screen_Cols * 2) / 3;
   Slrn_Full_Screen_Update = 1;
}

/*{{{ rot13 and spoilers */
static void toggle_rot13 (void)
{
   Do_Rot13 = !Do_Rot13;
   Slrn_Full_Screen_Update = 1;
}

 
#if SLRN_HAS_SPOILERS
static void show_spoilers (void)
{
   Slrn_Article_Line_Type *l1 = NULL;
   Slrn_Article_Line_Type *l = Slrn_Article_Lines;
   
   do
     {
	/* first find the first spoiler-ed line */
	while ((l != NULL) && 
	       (0 == (l->flags & SPOILER_LINE)))
	  {
	     l = l->next;
	  }
	l1 = l;
	/* now un-spoiler until we hit an un-spoiler-ed line */
	while ((l != NULL) && (l->flags & SPOILER_LINE))
	  {
	     l->flags &= ~SPOILER_LINE;
	     l = l->next;
	  }
     } /* Prefix arg means un-spoiler the whole article */
   while ((Slrn_Prefix_Arg_Ptr != NULL) && (l != NULL));
   Slrn_Prefix_Arg_Ptr = NULL;
   if (l1 != NULL) Current_Line = l1;
   Slrn_Full_Screen_Update = 1;
}
#endif


/*}}}*/

/*{{{ hide/toggle quotes */

static void hide_quotes (void)
{
   Slrn_Article_Line_Type *l = Slrn_Article_Lines, *last = NULL;
   while (l != NULL)
     {
	if (l->flags & QUOTE_LINE)
	  {
	     if (Quotes_Hidden && (last != NULL)) l->flags |= HIDDEN_LINE;
	     else l->flags &= ~HIDDEN_LINE;
	     last = l;
	  }
	else last = NULL;
	l = l->next;
     }
   find_article_line_number ();
   count_article_lines ();
   Slrn_Full_Screen_Update = 1;
}

/* This function needs generalized on an article by article basis */
static void toggle_quotes (void)
{
   Quotes_Hidden = !Quotes_Hidden;
   hide_quotes ();
}
/*}}}*/


static void toggle_headers (void)
{
   Headers_Hidden_Mode = !Headers_Hidden_Mode;
   hide_art_headers ();
   if (Headers_Hidden_Mode == 0) art_bob ();
   Slrn_Full_Screen_Update = 1;
}

/*}}}*/
/*{{{ header window display modes */

static void toggle_show_author (void)
{
   Slrn_Show_Author++;
   Slrn_Show_Author = (Slrn_Show_Author % 3);
   Slrn_Full_Screen_Update = 1;
}

/*}}}*/

/*{{{ leave/suspend article mode and support functions */

/*{{{ update_ranges */

static void update_ranges (void)
{
   int min, max;
   int queued = 0;
   Slrn_Range_Type *r, *r_save;
   Slrn_Header_Type *h = Slrn_First_Header;
   
   if (User_Aborted_Group_Read) return;
   
   /* skip articles for which the numeric id was not available */
   while ((h != NULL) && (h->number < 0)) h = h->real_next;
   if (h == NULL) return;
   
   /* we are creating new ranges so steal old. */
   r_save = r = Current_Group->range.next;
   Current_Group->range.next = NULL;
   
   /* fill in range for articles prior to current group of headers.  This
    * is done in two parts.  First, mark as read everything up to the
    * first article on the server plus what we have read after that.  Then,
    * fill in the gap for articles on the server up to this group.
    */
   
   max = Slrn_Server_Min - 1;
   min = h->number;	       /* starting number of headers */
   
   /* Part one.  Skip past ranges that are below the server minimum number.
    * They will be fused together.
    */
   while ((r != NULL) && (r->min <= max))
     {
	if (r->max >= max)
	  {
	     max = r->max;
	     if (max >= min) max = min - 1;
	     r = r->next;
	     break;
	  }
	r = r->next;
     }
   slrn_add_group_ranges (Current_Group, 1, max);
   
   /* second part */
   while ((r != NULL) && (r->min < min))
     {
	max = r->max;
	if (max >= min) max = min - 1;
	slrn_add_group_ranges (Current_Group, r->min, max);
	r = r->next;
     }
   
   /* Now handle ranges in the current group. We no longer need the ranges
    * so free them.
    */
   if (r_save != NULL)
     {
	r = r_save;
	while (r != NULL)
	  {
	     r_save = r->next;
	     SLFREE (r);
	     r = r_save;
	  }
     }
   
   
   queued = 0;
   max = Slrn_Server_Max;
   while (h != NULL)
     {
	max = h->number;
	if (h->flags & HEADER_READ)
	  {
	     queued = 1;
	  }
	else
	  {
	     if (queued || (min != max))
	       {
		  slrn_add_group_ranges (Current_Group, min, max - 1);
		  queued = 0;
	       }
	     
	     min = max + 1;	       /* mark next number as start of
					* a read range.  We mark it now
					* because h->next->number might not
					* be max + 1.
					*/
	  }
	h = h->real_next;
     }
   
   
   if (queued == 0) min = max + 1;
   slrn_add_group_ranges (Current_Group, min, Slrn_Server_Max);
}

/*}}}*/

/*{{{ art_quit */
static void art_quit (void)
{
   Slrn_Header_Type *h = Headers, *next;

#if SLRN_HAS_GROUPLENS
   if (Slrn_Use_Group_Lens) slrn_put_grouplens_scores ();
#endif
   
   free_article ();
   
   free_kill_lists_and_update ();
   free_tag_list ();
   
   slrn_close_score ();
   
   if (h != NULL)
     {
	update_ranges ();
     }
   
   while (h != NULL)
     {
	next = h->next;
	SLFREE (h->subject);
	SLFREE (h);
	h = next;
     }
   
   Headers = Slrn_Current_Header = NULL;
   Header_Number = Num_Headers = 0;
   Current_Group = NULL;
   Last_Read_Header = NULL;
   *Output_Filename = 0;
   
      
   Same_Subject_Start_Header = NULL;
   Slrn_Current_Group_Name = NULL;
   
   Slrn_Current_Mode = Last_Current_Mode;
   Slrn_Full_Screen_Update = 1;
}

/*}}}*/

static void skip_to_next_group (void)
{
   art_quit ();
   slrn_select_next_group ();
}

static void skip_to_prev_group (void)
{
   art_quit ();
   slrn_select_prev_group ();
}

static void fast_quit (void)
{
   art_quit ();
   slrn_group_quit ();
}

static void art_suspend_cmd (void)
{
   int rows = SLtt_Screen_Rows;
   slrn_suspend_cmd ();
   if (rows != SLtt_Screen_Rows)
     art_winch_sig ();
}

/*}}}*/
/*{{{ art_xpunge */
static void art_xpunge (void)
{
   Slrn_Header_Type *save, *next, *h;
   
   free_article ();
   free_kill_lists_and_update ();
   
   save = Headers;
   if (Headers != NULL)
     {
	update_ranges ();
     }
   
   while (Headers != NULL)
     {
	if (0 == (Headers->flags & HEADER_READ))
	  break;
	Headers = Headers->next;
     }
   
   if (Headers == NULL)
     {
	Headers = save;
	art_quit ();
	return;
     }
   
   if ((Num_Tag_List.len != 0)
       && (Num_Tag_List.headers != NULL))
     {
	unsigned int i, j;
	Slrn_Header_Type *th;
	
	j = 0;
	for (i = 0; i < Num_Tag_List.len; i++)
	  {
	     th = Num_Tag_List.headers[i];
	     if (th->flags & HEADER_READ)
	       {
		  th->tag_number = 0;
		  th->flags &= ~HEADER_NTAGGED;
		  continue;
	       }
	     
	     Num_Tag_List.headers [j] = th;
	     j++;
	     th->tag_number = j;
	  }
	Num_Tag_List.len = j;
     }
   
   next = Slrn_Current_Header;
   while (next != NULL)
     {
	if (0 == (next->flags & HEADER_READ))
	  break;
	next = next->next;
     }
   
   if (next == NULL)
     {
	next = Slrn_Current_Header;
	while (next != NULL)
	  {
	     if (0 == (next->flags & HEADER_READ))
	       break;
	     next = next->prev;
	  }
     }
   
   Slrn_Current_Header = next;	       /* cannot be NULL */
   
   if (Top_Header->flags & HEADER_READ) 
     Top_Header = NULL;

   h = Slrn_First_Header;
   /* h cannot be NULL here*/
   while (1)
     {
	next = h->real_next;
	if (0 == (h->flags & HEADER_READ))
	  break;
	SLFREE (h->subject);
	SLFREE (h);
	h = next;
     }
   Slrn_First_Header = h;
   h->real_prev = NULL;
   
   while (h != NULL)
     {	
	Slrn_Header_Type *next_next;

	next = h->real_next;
	while (next != NULL)
	  {
	     next_next = next->real_next;
	     if (0 == (next->flags & HEADER_READ))
	       break;
	     SLFREE (next->subject);
	     SLFREE (next);
	     next = next_next;
	  }
	h->real_next = next;
	if (next != NULL)
	  next->real_prev = h;
	h = next;
     }
   
   Last_Read_Header = NULL;
   Num_Headers = 0;
   h = Headers = Slrn_First_Header;
   Headers->prev = NULL;
   
   while (h != NULL)
     {
	Num_Headers++;
	h->next = next = h->real_next;
	if (next != NULL)
	  {
	     next->prev = h;
	  }
	h = next;
     }
   
   sort_by_sorting_mode ();
   Slrn_Full_Screen_Update = 1;
}

/*}}}*/
/*{{{ cancel_article */

static void cancel_article (void)
{
   char *from, *msgid, *newsgroups, *dist;
   char me[256];
   
   if (-1 == select_article (0)) return;

   art_update_screen ();
   (void) slrn_smg_refresh ();
   
   if (slrn_get_yesno (0, "Are you sure that you want to cancel this article") <= 0)
     return;
   
   slrn_message ("Cancelling...");  slrn_smg_refresh ();
   
   /* TO cancel, we post a cancel message with a 'control' header.  First, check to
    * see if this is really the owner of the message.
    */
   
   from = extract_header ("From: ", 6);
   if (from != NULL) from = parse_from (from);
   if (from == NULL) from = "";
   sprintf (me, "%s@%s", Slrn_User_Info.username, Slrn_User_Info.host);
   
   if (slrn_case_strcmp ((unsigned char *) from, (unsigned char *) me))
     {
	slrn_error ("Failed: Your name: '%s' is not '%s'", me, from);
	return;
     }
   
   if (NULL == (newsgroups = extract_header ("Newsgroups: ", 12)))
     newsgroups = "";
   
   if (NULL == (msgid = extract_header ("Message-ID: ", 12)))
     {
	slrn_error ("No message id.");
	return;
     }
   
   
   dist = extract_header("Distribution: ", 14);
   
   if (Slrn_Post_Obj->po_start () < 0) return;
   
   Slrn_Post_Obj->po_printf ("From: %s\r\nNewsgroups: %s\r\nSubject: cancel %s\r\nControl: cancel %s\r\n",
		from, newsgroups, msgid, msgid);
   
   if (dist != NULL)
     {
	Slrn_Post_Obj->po_printf ("Distribution: %s\r\n", dist);
     }
   
   Slrn_Post_Obj->po_printf("\r\nignore\r\nArticle canceled by slrn %s\r\n", Slrn_Version);
   
   if (0 == Slrn_Post_Obj->po_end ())
     {
	slrn_message ("Done.");
     }
}


/*}}}*/

/*{{{ header/thread (un)deletion/(un)catchup */
static void delete_header (Slrn_Header_Type *h)
{
   if (h->flags & HEADER_TAGGED) return;
   if (0 == (h->flags & HEADER_READ))
     {
	kill_cross_references (h);
	h->flags |= HEADER_READ;
     }
   Group_Modified = 1;
}

static void undelete_header (Slrn_Header_Type *h)
{
   if (h->flags & HEADER_TAGGED) return;
   h->flags &= ~HEADER_READ;
   Group_Modified = 1;
}

static void catch_up_all (void)
{
   for_all_headers (delete_header, 1);
   Slrn_Full_Screen_Update = 1;
   Group_Modified = 1;
}

static void un_catch_up_all (void)
{
   for_all_headers (undelete_header, 1);
   Slrn_Full_Screen_Update = 1;
   Group_Modified = 1;
}

static void catch_up_to_here (void)
{
   for_all_headers (delete_header, 0);
   Slrn_Full_Screen_Update = 1;
   Group_Modified = 1;
}

static void un_catch_up_to_here (void)
{
   for_all_headers (undelete_header, 0);
   Slrn_Full_Screen_Update = 1;
   Group_Modified = 1;
}


static void undelete_header_cmd (void)
{
   if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
       || (Slrn_Current_Header->child == NULL)/* At top with no child */
       /* or at top with child showing */
       || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
     {
	undelete_header (Slrn_Current_Header);
     }
   else
     {
	for_this_tree (Slrn_Current_Header, undelete_header);
     }
   slrn_header_down_n (1, 0);
   Slrn_Full_Screen_Update = 1;
}

static void delete_header_cmd (void)
{
   if ((Slrn_Current_Header->parent != NULL)/* in middle of thread */
       || (Slrn_Current_Header->child == NULL)/* At top with no child */
       /* or at top with child showing */
       || (0 == (Slrn_Current_Header->child->flags & HEADER_HIDDEN)))
     {
	delete_header (Slrn_Current_Header);
     }
   else
     {
	for_this_tree (Slrn_Current_Header, delete_header);
     }
   next_unread ();
   Slrn_Full_Screen_Update = 1;
}

static void thread_delete_cmd (void)
{
   for_this_tree (Slrn_Current_Header, delete_header);
   delete_header_cmd ();
}


/*}}}*/

/*{{{ group_lens functions */
#if SLRN_HAS_GROUPLENS
static void grouplens_rate_article (void)
{
   int ch;
   
   if ((Slrn_Current_Header == NULL)
       || (Num_GroupLens_Rated == -1))
     return;
   
   slrn_message ("Rate article (1-5):");
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
   
   ch = SLang_getkey ();
   if ((ch < '1') || (ch > '5'))
     {
	slrn_error ("Rating must be in range 1 to 5.");
	return;
     }
   
   slrn_group_lens_rate_article (Slrn_Current_Header, ch - '0',
				 (Article_Visible && (Header_Showing == Slrn_Current_Header)));
}
#endif
/*}}}*/
/*{{{ mouse commands */

/* actions for different regions:
 *	- top status line (help)
 *	- header status line
 *	- above header status line
 *	- below header status line
 *	- bottom status line
 */
static void art_mouse (void (*top_status)(void),
		       void (*header_status)(void),
		       void (*bot_status)(void),
		       void (*normal_region)(void)
		       )
{
   int r, c;
   slrn_get_mouse_rc (&r, &c);
   
   /* take top status line into account */
   if (r == 1)
     {
	if (Slrn_Use_Mouse)
	  (void) slrn_execute_menu (c, Art_Functions_Ptr);
	else if (NULL != top_status) (*top_status) ();
 	return;
     }
   
   if (r >= SLtt_Screen_Rows)
     return;
   
   /* On header status line */
   if (r - 2 == Header_Window_Height)
     {
	if (NULL != header_status) (*header_status) ();
 	return;
     }
   
   /* bottom status line */
   if (r == SLtt_Screen_Rows - 1)
     {
	if (NULL != bot_status) (*bot_status) ();
	return;
     }
   
   if (r - 2 > Header_Window_Height)
     {
	if (NULL != normal_region) (*normal_region) ();
 	return;
     }
   
   r -= (1 + Last_Cursor_Row);
   if (r < 0)
     {
	r = -r;
	if (r != slrn_header_up_n (r, 0)) return;
     }
   else if (r != slrn_header_down_n (r, 0)) return;
   
   select_article (1);
   /* if (NULL != normal_region) (*normal_region) (); */
   
}


static void art_mouse_left (void)
{
   art_mouse (slrn_article_help, header_pagedn,
	      art_next_unread, art_pagedn);
}

static void art_mouse_middle (void)
{
   art_mouse (toggle_show_author, hide_article,
	      toggle_quotes, hide_article);
#if 1
   /* Make up for buggy rxvt which have problems with the middle key. */
   if (NULL != getenv ("COLORTERM"))
     {
	if (SLang_input_pending (7))
	  {
	     while (SLang_input_pending (0)) SLang_getkey ();
	  }
     }
#endif
}


static void art_mouse_right (void)
{
   art_mouse (slrn_article_help, header_pageup,
	      art_prev_unread, art_pageup);
}

/*}}}*/

/*{{{ slrn_init_article_mode */

#define A_KEY(s, f)  {s, (int (*)(void)) f}

static SLKeymap_Function_Type Art_Functions [] =
{
#if SLRN_HAS_GROUPLENS
   A_KEY("grouplens_rate_article", grouplens_rate_article),
#endif
   A_KEY("browse_url", browse_url),
   A_KEY("browse_url", browse_url),
   A_KEY("art_xpunge", art_xpunge),
   A_KEY("wrap_article", wrap_article),
   A_KEY("goto_last_read", goto_last_read),
#if SLRN_HAS_DECODE
   A_KEY("decode", decode_article),
#endif
#if 0
#if SLRN_HAS_SPOILERS
     A_KEY("show_spoilers", show_spoilers),
#endif
#endif
     A_KEY("create_score", create_score),
     A_KEY("toggle_collapse_threads", toggle_collapse_threads),
     A_KEY("toggle_header_tag", toggle_header_tag),
     A_KEY("tag_header", num_tag_header),
     A_KEY("untag_headers", num_untag_headers),
     A_KEY("repeat_last_key", slrn_repeat_last_key),
     A_KEY("goto_beginning", art_bob),
     A_KEY("goto_end", art_eob),
     A_KEY("forward_digest", skip_digest_forward),
     A_KEY("locate_article", locate_header_by_msgid),
     A_KEY("delete_thread", thread_delete_cmd),
     A_KEY("post", slrn_post_cmd),
     A_KEY("get_children_headers", get_children_headers),
     A_KEY("get_parent_header", get_parent_header),
     A_KEY("skip_to_next_group", skip_to_next_group),
     A_KEY("skip_to_prev_group", skip_to_prev_group),
     A_KEY("fast_quit", fast_quit),
     A_KEY("catchup_all", catch_up_all),
     A_KEY("uncatchup_all", un_catch_up_all),
     A_KEY("catchup", catch_up_to_here),
     A_KEY("uncatchup", un_catch_up_to_here),
     A_KEY("pipe_article", pipe_article),
     A_KEY("toggle_rot13", toggle_rot13),
     A_KEY("toggle_show_author", toggle_show_author),
     A_KEY("toggle_sort", toggle_sort),
     A_KEY("skip_quotes", skip_quoted_text),
     A_KEY("art_header_bob", art_header_bob),
     A_KEY("art_header_eob", art_header_eob),
     A_KEY("goto_article", goto_article),
     A_KEY("shrink_window", shrink_window),
     A_KEY("enlarge_window", enlarge_window),
     A_KEY("article_pagedn", art_pagedn),
     A_KEY("article_pageup", art_pageup),
     A_KEY("article_linedn", art_linedn),
     A_KEY("article_lineup", art_lineup),
     A_KEY("article_search", article_search),
     A_KEY("author_search_backward", author_search_backward),
     A_KEY("author_search_forward", author_search_forward),
     A_KEY("cancel", cancel_article),
     A_KEY("delete", delete_header_cmd),
     A_KEY("down", header_down),
     A_KEY("exchange_mark", exchange_mark),
     A_KEY("followup", followup),
     A_KEY("forward", forward),
     A_KEY("help", slrn_article_help),
     A_KEY("hide_article", hide_article),
     A_KEY("left", art_left),
     A_KEY("mark_spot", mark_spot),
     A_KEY("next", art_next_unread),
     A_KEY("prev", art_prev_unread),
     A_KEY("quit", art_quit),
     A_KEY("redraw", slrn_redraw),
     A_KEY("reply", reply_cmd),
     A_KEY("right", art_right),
     A_KEY("save", save_article),
     A_KEY("scroll_dn", art_pagedn),
     A_KEY("scroll_up", art_pageup),
     A_KEY("subject_search_backward", subject_search_backward),
     A_KEY("subject_search_forward", subject_search_forward),
     A_KEY("suspend", art_suspend_cmd),
     A_KEY("toggle_headers", toggle_headers),
     A_KEY("toggle_quotes", toggle_quotes),
     A_KEY("undelete", undelete_header_cmd),
     A_KEY("up", header_up),
     A_KEY("pageup", header_pageup),
     A_KEY("pagedn", header_pagedn),
     A_KEY("next_same_subject", next_header_same_subject),
     A_KEY("next_high_score", next_high_score),
     A_KEY("next_high_score", next_high_score),
     A_KEY("locate_header_by_msgid", locate_header_by_msgid),
     A_KEY(NULL, NULL)
};


Slrn_Mode_Type Art_Mode_Cap = 
{
   NULL,			       /* keymap */
   art_update_screen,
   art_winch_sig,
   slrn_art_hangup,
   SLRN_ARTICLE_MODE
};

void slrn_init_article_mode (void)
{
   char  *err = "Unable to create Article keymap!";
   char numbuf[2];
   char ch;
   
   if (NULL == (Slrn_Article_Keymap = SLang_create_keymap ("article", NULL)))
     slrn_exit_error (err);
   
   Art_Mode_Cap.keymap = Slrn_Article_Keymap;
   
   Slrn_Article_Keymap->functions = Art_Functions;
   Art_Functions_Ptr = Art_Functions;
   
   numbuf[1] = 0;
   
   for (ch = '0'; ch <= '9'; ch++)
     {
	numbuf[0] = ch;
	SLkm_define_key (numbuf, (FVOID_STAR) goto_header_number, Slrn_Article_Keymap);
     }
#if SLRN_HAS_GROUPLENS
   numbuf[0] = '0';
   /* Steal '0' for use as a prefix for rating. */
   SLkm_define_key  (numbuf, (FVOID_STAR) grouplens_rate_article, Slrn_Article_Keymap);
#endif
   
   SLkm_define_key  ("\033l", (FVOID_STAR) locate_header_by_msgid, Slrn_Article_Keymap);
   SLkm_define_key ("\0331", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0332", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0333", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0334", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0335", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0336", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0337", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0338", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0339", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key ("\0330", (FVOID_STAR) digit_arg, Slrn_Article_Keymap);
   SLkm_define_key  ("*", (FVOID_STAR) toggle_header_tag, Slrn_Article_Keymap);
   SLkm_define_key  ("#", (FVOID_STAR) num_tag_header, Slrn_Article_Keymap);
   SLkm_define_key  ("\033#", (FVOID_STAR) num_untag_headers, Slrn_Article_Keymap);
#if SLRN_HAS_DECODE
   SLkm_define_key  (":", (FVOID_STAR) decode_article, Slrn_Article_Keymap);
#endif
   SLkm_define_key  (";", (FVOID_STAR) mark_spot, Slrn_Article_Keymap);
   SLkm_define_key  (",", (FVOID_STAR) exchange_mark, Slrn_Article_Keymap);
   SLkm_define_key  ("g", (FVOID_STAR) skip_digest_forward, Slrn_Article_Keymap);
   SLkm_define_key  ("s", (FVOID_STAR) subject_search_forward, Slrn_Article_Keymap);
   SLkm_define_key  ("S", (FVOID_STAR) subject_search_backward, Slrn_Article_Keymap);
   SLkm_define_key  ("a", (FVOID_STAR) author_search_forward, Slrn_Article_Keymap);
   SLkm_define_key  ("A", (FVOID_STAR) author_search_backward, Slrn_Article_Keymap);
   SLkm_define_key  ("/", (FVOID_STAR) article_search, Slrn_Article_Keymap);
   SLkm_define_key  ("\033^C", (FVOID_STAR) cancel_article, Slrn_Article_Keymap);
   SLkm_define_key  ("o", (FVOID_STAR) save_article, Slrn_Article_Keymap);
   SLkm_define_key  ("|", (FVOID_STAR) pipe_article, Slrn_Article_Keymap);
   SLkm_define_key  ("?", (FVOID_STAR) slrn_article_help, Slrn_Article_Keymap);
   SLkm_define_key  ("H", (FVOID_STAR) hide_article, Slrn_Article_Keymap);
   SLkm_define_key  ("L", (FVOID_STAR) goto_last_read, Slrn_Article_Keymap);
   SLkm_define_key  ("N", (FVOID_STAR) skip_to_next_group, Slrn_Article_Keymap);
   SLkm_define_key  ("n", (FVOID_STAR) art_next_unread, Slrn_Article_Keymap);
   SLkm_define_key  ("j", (FVOID_STAR) goto_article, Slrn_Article_Keymap);
   SLkm_define_key  ("p", (FVOID_STAR) art_prev_unread, Slrn_Article_Keymap);
   SLkm_define_key  ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("d", (FVOID_STAR) delete_header_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("u", (FVOID_STAR) undelete_header_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("U", (FVOID_STAR) browse_url, Slrn_Article_Keymap);
   SLkm_define_key  ("x", (FVOID_STAR) art_xpunge, Slrn_Article_Keymap);
   SLkm_define_key  ("t", (FVOID_STAR) toggle_headers, Slrn_Article_Keymap);
   SLkm_define_key  ("T", (FVOID_STAR) toggle_quotes, Slrn_Article_Keymap);
   SLkm_define_key  ("\033a", (FVOID_STAR) toggle_show_author, Slrn_Article_Keymap);
   SLkm_define_key  ("\033d", (FVOID_STAR) thread_delete_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("\033p", (FVOID_STAR) get_parent_header, Slrn_Article_Keymap);
   SLkm_define_key  ("\033^P", (FVOID_STAR) get_children_headers, Slrn_Article_Keymap);
   SLkm_define_key  ("\033t", (FVOID_STAR) toggle_collapse_threads, Slrn_Article_Keymap);
   SLkm_define_key  ("\t", (FVOID_STAR) skip_quoted_text, Slrn_Article_Keymap);
   SLkm_define_key  (" ", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
   SLkm_define_key  (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Article_Keymap);
   SLkm_define_key  ("\r", (FVOID_STAR) art_pagedn, Slrn_Article_Keymap);
#ifdef __os2__
   SLkm_define_key  ("^@S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0S", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
#else
   SLkm_define_key  ("^?", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
#endif
   SLkm_define_key  ("b", (FVOID_STAR) art_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("q", (FVOID_STAR) art_quit, Slrn_Article_Keymap);
   SLkm_define_key  ("F", (FVOID_STAR) forward, Slrn_Article_Keymap);
   SLkm_define_key  ("f", (FVOID_STAR) followup, Slrn_Article_Keymap);
   SLkm_define_key  ("K", (FVOID_STAR) create_score, Slrn_Article_Keymap);
   SLkm_define_key  ("W", (FVOID_STAR) wrap_article, Slrn_Article_Keymap);
   SLkm_define_key  ("r", (FVOID_STAR) reply_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("=", (FVOID_STAR) next_header_same_subject, Slrn_Article_Keymap);
   SLkm_define_key  ("!", (FVOID_STAR) next_high_score, Slrn_Article_Keymap);
   SLkm_define_key  ("<", (FVOID_STAR) art_bob, Slrn_Article_Keymap);
   SLkm_define_key  (">", (FVOID_STAR) art_eob, Slrn_Article_Keymap);
   SLkm_define_key  ("^R", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
   SLkm_define_key  ("^L", (FVOID_STAR) slrn_redraw, Slrn_Article_Keymap);
   SLkm_define_key  ("^Z", (FVOID_STAR) art_suspend_cmd, Slrn_Article_Keymap);
   SLkm_define_key  ("^P", (FVOID_STAR) header_up, Slrn_Article_Keymap);
   SLkm_define_key  ("^M", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
#ifdef __os2__
   SLkm_define_key  ("\033^@H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033\xE0H", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033^@P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
   SLkm_define_key  ("\033\xE0P", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
   SLkm_define_key  ("^@H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0H", (FVOID_STAR) header_up, Slrn_Article_Keymap);
   SLkm_define_key  ("^@P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0P", (FVOID_STAR) header_down, Slrn_Article_Keymap);
   SLkm_define_key  ("^@M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0M", (FVOID_STAR) art_right, Slrn_Article_Keymap);
   SLkm_define_key  ("^@K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0K", (FVOID_STAR) art_left, Slrn_Article_Keymap);
#else
   SLkm_define_key  ("\033\033[A", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033\033OA", (FVOID_STAR) art_lineup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033\033[B", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
   SLkm_define_key  ("\033\033OB", (FVOID_STAR) art_linedn, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[A", (FVOID_STAR) header_up, Slrn_Article_Keymap);
   SLkm_define_key  ("\033OA", (FVOID_STAR) header_up, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[B", (FVOID_STAR) header_down, Slrn_Article_Keymap);
   SLkm_define_key  ("\033OB", (FVOID_STAR) header_down, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[C", (FVOID_STAR) art_right, Slrn_Article_Keymap);
   SLkm_define_key  ("\033OC", (FVOID_STAR) art_right, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[D", (FVOID_STAR) art_left, Slrn_Article_Keymap);
   SLkm_define_key  ("\033OD", (FVOID_STAR) art_left, Slrn_Article_Keymap);
#endif
   SLkm_define_key  ("\033S", (FVOID_STAR) toggle_sort, Slrn_Article_Keymap);
   SLkm_define_key  ("^U", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033V", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
#ifdef __os2__
   SLkm_define_key  ("^@I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0I", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("^@Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
   SLkm_define_key  ("\xE0Q", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
#else
   SLkm_define_key  ("\033[5~", (FVOID_STAR) header_pageup, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[6~", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
#endif
   SLkm_define_key  ("^D", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
   SLkm_define_key  ("^V", (FVOID_STAR) header_pagedn, Slrn_Article_Keymap);
   SLkm_define_key  ("\033>", (FVOID_STAR) art_header_eob, Slrn_Article_Keymap);
   SLkm_define_key  ("\033<", (FVOID_STAR) art_header_bob, Slrn_Article_Keymap);
   SLkm_define_key  ("c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
   SLkm_define_key  ("\033c", (FVOID_STAR) catch_up_all, Slrn_Article_Keymap);
   SLkm_define_key  ("\033u", (FVOID_STAR) un_catch_up_all, Slrn_Article_Keymap);
   SLkm_define_key  ("\033C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
   SLkm_define_key  ("C", (FVOID_STAR) catch_up_to_here, Slrn_Article_Keymap);
   SLkm_define_key  ("\033U", (FVOID_STAR) un_catch_up_to_here, Slrn_Article_Keymap);
   SLkm_define_key  ("\033R", (FVOID_STAR) toggle_rot13, Slrn_Article_Keymap);
#if 0
#if SLRN_HAS_SPOILERS
   SLkm_define_key  ("\033?", (FVOID_STAR) show_spoilers, Slrn_Article_Keymap);
#endif
#endif
   SLkm_define_key  ("^N", (FVOID_STAR) header_down, Slrn_Article_Keymap);
   SLkm_define_key  ("^", (FVOID_STAR) enlarge_window, Slrn_Article_Keymap);
   SLkm_define_key  ("^^", (FVOID_STAR) shrink_window, Slrn_Article_Keymap);
   
   /* mouse (left/middle/right) */
   SLkm_define_key  ("\033[M\040", (FVOID_STAR) art_mouse_left, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[M\041", (FVOID_STAR) art_mouse_middle, Slrn_Article_Keymap);
   SLkm_define_key  ("\033[M\042", (FVOID_STAR) art_mouse_right, Slrn_Article_Keymap);
   
   if (SLang_Error) slrn_exit_error (err);
}


/*}}}*/
/*{{{ slrn_article_mode and support functions */


static void slrn_art_hangup (int sig)
{
   if (Slrn_Current_Header != NULL)
     undelete_header_cmd ();		       /* in case we are reading one */
   art_quit ();
   if (Slrn_Group_Hangup_Hook != NULL) (*Slrn_Group_Hangup_Hook) (sig);
   slrn_quit (sig);
}

/*{{{ mark_ranges_read */
static void mark_ranges_read (Slrn_Range_Type *r)
{
   Slrn_Header_Type *h = Slrn_First_Header;
   int min, max;
   
   while ((r != NULL) && (h != NULL))
     {
	min = r->min;
	max = r->max;
	while (h != NULL)
	  {
	     if (h->number < min)
	       {
		  h = h->real_next;
		  continue;
	       }
	     
	     if (h->number > max)
	       {
		  break;
	       }
	     
	     h->flags |= HEADER_READ;
	     h = h->real_next;
	  }
	r = r->next;
     }
}

/*}}}*/


/*{{{ slrn_select_article_mode */
/* If all > 0, get last 'all' headers from server independent of whether
 *             they have been read or not.
 * If all < 0, and this is not the first time this group has been accessed,
 *             either during this session or during previous sessions, get
 *             that last 'all' UNREAD articles.
 * Otherwise,  fetch ALL UNREAD headers from the server.
 */
int slrn_select_article_mode (Slrn_Group_Type *g, int all, int score)
{
   int min, max;
   int smin, smax;
   Slrn_Range_Type *r;
   
   User_Aborted_Group_Read = 0;
   Header_Number = 0;
   Headers = Slrn_First_Header = NULL;
   Threads_Collapsed = 0;
   Same_Subject_Start_Header = NULL;
   
   Current_Group = g;
   r = &g->range;
   Slrn_Current_Group_Name = g->name;
   Slrn_Score_After_XOver = Slrn_Server_Obj->sv_has_xover;
   
   Number_Killed = Number_High_Scored = Number_Low_Scored = 0;

   if (score && (1 == slrn_open_score (Slrn_Current_Group_Name)))
     Perform_Scoring = 1;
   else Perform_Scoring = 0;
   
   Slrn_Server_Min = r->min;
   Slrn_Server_Max = r->max;
   r = r->next;
   /* Now r points to ranges already read.  */
   
   if (all > 0)
     {
	min = Slrn_Server_Max - all + 1;
	if (min < Slrn_Server_Min) min = Slrn_Server_Min;
	(void) get_headers (min, Slrn_Server_Max, &all);
	mark_ranges_read (r);
     }
   else
     {
	if ((all < 0) && (r != NULL))
	  {
	     int unread;

	     /* This condition will occur when the user wants to read unread
	      * articles that occur in a gap, i.e., RRRUUUUURRRUUUUUUU and
	      * we need to dig back far enough below the last group of read
	      * ones until we have retrieved abs(all) articles.
	      * 
	      * The problem with this is that some articles may not be 
	      * available on the server which means that the number to
	      * go back will be under estimated.
	      */
	     all = -all;
	     
	     while (r->next != NULL) r = r->next;
	     /* Go back through previously read articles counting unread.
	      * If number unread becomes greater than the number that we
	      * intend to read, then we know where to start querying 
	      * the server.
	      */
	     unread = 0;
	     max = Slrn_Server_Max;
	     while (r->prev != NULL)
	       {
		  unread += max - r->max;
		  if (unread >= all) break;
		  max = r->min - 1;
		  r = r->prev;
	       }
	     
	     if (unread >= all)
	       {
		  /* This may be problematic if some articles are missing on 
		   * the server.  If that is the case, smin will be to high
		   * and we will fall short of the goal.
		   */
		  smin = r->max + (unread - all) + 1;
	       }
	     else smin = Slrn_Server_Min;
	     smax = Slrn_Server_Max;
	     r = r->next;
	  }
	else
	  {
	     /* all == 0, or no previously read articles. */
	     smin = Slrn_Server_Min;
	     smax = Slrn_Server_Max;
	     if (r != NULL)
	       {
		  Slrn_Range_Type *r1;
		  
		  /* Estimate how many are available to read */
		  all = smax - r->max;
#if 0				       /* is this correct?? */
		  all++;
#endif
		  
		  /* Now subtract the ones that we have already read. */
		  r1 = r->next;
		  while (r1 != NULL)
		    {
		       all -= (r1->max - r1->min) + 1;
		       r1 = r1->next;
		    }
		  /* This condition should never arise */
		  if (all == 0) all = smax - smin + 1;
	       }
	     else all = smax - smin + 1;
	  }
	
	while (r != NULL)
	  {
	     if (r->min > smin)
	       {
		  min = smin;
		  max = r->min - 1;
		  if (get_headers (min, max, &all) == -1)
		    {
		       if (SLang_Error == USER_BREAK)
			 break;
		       
		       Slrn_Groups_Dirty = 1;
		       r->min = min;
		    }
		  
		  smin = r->max + 1;
	       }
	     else
	       {
		  smin = r->max + 1;
	       }
	     r = r->next;
	  }
	
	if (smin <= smax)
	  {
	     if (-1 == get_headers (smin, smax, &all))
	       {
	       }
	  }
     }
   
   if (SLang_Error == USER_BREAK) 
     {
	slrn_error ("Group transfer aborted.");
	Slrn_Server_Obj->sv_close ();
	/* This means that we cannot update ranges for this group because 
	 * the user aborted and update_ranges assumes that all articles 
	 * upto server max are present.
	 */
	User_Aborted_Group_Read = 1;
	art_quit ();
     }
   else if (Slrn_Score_After_XOver 
       && Perform_Scoring)
     score_headers ();
   
   if (Headers == NULL)
     {
	slrn_close_score ();
	free_kill_lists_and_update ();
	slrn_error ("No unread articles found.");
	Slrn_Current_Group_Name = NULL;
	if (SLang_Error == USER_BREAK) return -1;
	else return -2;
     }
   
   make_hash_table ();
   sort_by_sorting_mode ();
   
   extract_real_names ();
   
   Last_Current_Mode = Slrn_Current_Mode;
   Slrn_Group_Hangup_Hook = Slrn_Current_Mode->hangup_fun;
   Slrn_Current_Mode = &Art_Mode_Cap;

   Slrn_Current_Header = Headers;
   Header_Number = 1;
   Top_Header = NULL;
   Last_Cursor_Row = 0;
   Group_Modified = 0;
   Mark_Header = NULL;
   
   Top_Line = Current_Line = Slrn_Article_Lines = NULL;
   At_End_Of_Article = NULL;
   Header_Showing = NULL;
   Article_Visible = 0;
   art_winch ();

#if SLRN_HAS_SLANG
   (void) SLang_run_hooks ("article_mode_hook", NULL, NULL);
#endif   
   
   
#if SLRN_HAS_GROUPLENS
   slrn_set_suspension (1);
   Num_GroupLens_Rated = slrn_get_grouplens_scores ();
   slrn_set_suspension (0);
#endif
   
   quick_help ();
   
   if (Slrn_Startup_With_Article) art_pagedn ();
   
   if (SLang_Error == 0)
     {
	if (Perform_Scoring 
	    /* && (Number_Killed || Number_High_Scored) */
	    )
	  {
#if SLRN_HAS_GROUPLENS
	     if (Num_GroupLens_Rated != -1)
	       {
		  slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u, Num GroupLens Rated: %d",
				Number_Killed, Number_High_Scored, Number_Low_Scored,
				Num_GroupLens_Rated);
	       }
	     else
#endif
	       slrn_message ("Num Killed: %u, Num High: %u, Num Low: %u",
			     Number_Killed, Number_High_Scored, Number_Low_Scored);
	  }
	
	else slrn_clear_message ();
     }
   
   Number_Low_Scored = Number_Killed = Number_High_Scored = 0;
   return 0;
}

/*}}}*/

/*}}}*/

/*{{{ screen update functions */

static void quick_help (void) /*{{{*/
{
   char *msg;
   if (Article_Visible == 0)
     msg = "SPC:Select  Ctrl-D:PgDn  Ctrl-U:PgUp  d:Mark-as-Read  n:Next  p:Prev  q:Quit";
   else
     msg = "SPC:Pgdn  B:PgUp  u:Un-Mark-as-Read  f:Followup  n:Next  p:Prev  q:Quit";
   if (0 == slrn_message (msg))
     Slrn_Message_Present = 0;
}

/*}}}*/

static void write_rot13 (unsigned char *buf) /*{{{*/
{
   static int init_rot13;
   static char rot13buf[256];
   unsigned char ch;
   int i;
   
   if (init_rot13 == 0)
     {
	init_rot13 = 1;
	for (i = 0; i < 256; i++)
	  {
	     rot13buf[i] = i;
	  }
	
	for (i = 'A'; i <= 'M'; i++)
	  {
	     rot13buf[i] = i + 13;
	     /* Now take care of lower case ones */
	     rot13buf[i + 32] =  i + 32 + 13;
	  }
	
	for (i = 'N'; i <= 'Z'; i++)
	  {
	     rot13buf[i] = i - 13;
	     /* Now take care of lower case ones */
	     rot13buf[i + 32] =  i + 32 - 13;
	  }
     }
   
   while ((ch = *buf++) != 0)
     {
	ch = rot13buf[ch];
	SLsmg_write_nchars ((char *) &ch, 1);
     }
}

/*}}}*/

/*{{{ utility routines */

#if SLRN_HAS_SPOILERS
/* write out the line, replacing all printable chars with '*' */
static void write_spoiler (unsigned char *buf) /*{{{*/
{
   unsigned char ch;
   
   Spoilers_Visible = Slrn_Current_Header;
   
   if (Slrn_Spoiler_Char == ' ')
     return;
   
   while ((ch = *buf++) != 0)
     {
	if (!isspace(ch)) ch = Slrn_Spoiler_Char;
	SLsmg_write_nchars ((char *) &ch, 1);
     }
}
/*}}}*/
#endif

static void draw_tree (Slrn_Header_Type *h) /*{{{*/
{
   unsigned char *b;
   
#ifndef __os2__
   if (SLtt_Has_Alt_Charset == 0)
     {
	SLsmg_write_string ((char *) h->tree);
	SLsmg_write_string ("  ");
	return;
     }
   SLsmg_set_char_set (1);
#endif
   
   slrn_set_color (TREE_COLOR);
   
   if (*h->tree) SLsmg_write_string ((char *) h->tree);
   
   if (h->flags & FAKE_CHILDREN)
     {
	static unsigned char buf[2] = {SLSMG_UTEE_CHAR,SLSMG_HLINE_CHAR};
	b = buf;
	SLsmg_forward (-1);
	SLsmg_write_char (SLSMG_ULCORN_CHAR);
     }
   else if ((h->sister == NULL) ||
	    ((h->sister->flags & FAKE_PARENT) && ((h->flags & FAKE_PARENT) == 0)))
     {
	static unsigned char buf[2] = {SLSMG_LLCORN_CHAR,SLSMG_HLINE_CHAR};
	b = buf;
     }
   else
     {
	static unsigned char buf[2] = {SLSMG_LTEE_CHAR,SLSMG_HLINE_CHAR
	};
	b = buf;
     }
   SLsmg_write_nchars ((char *) b, 2);
#ifndef __os2__
   SLsmg_set_char_set (0);
#endif
}

/*}}}*/

/*{{{ check_subject */

/*
 * This checks if subjects should be printed (correctly I hope)
 * hacked: articles in a tree shouldn't display their subject if the
 *          subject is already displayed (i.e. at top)
 * To add: take more Re:'s into account (currently, one is allowed)
 */
int Slrn_Show_Thread_Subject = 0;
static int check_subject (Slrn_Header_Type *h)
{
   char *psubj, *subj;
   
   subj = h->subject;
   psubj = h->prev->subject;	       /* used to be: h->parent->subject */
   if ((subj == NULL) || (psubj == NULL)) return 1;
   
   if (((*subj | 0x20) == 'r')
       && ((*(subj  + 1) | 0x20) == 'e')
       && (*(subj  + 2) == ':'))
     {
	subj += 3;
	while (*subj == ' ') subj++;
     }
   
   if (((*psubj | 0x20) == 'r')
       && ((*(psubj  + 1) | 0x20) == 'e')
       && (*(psubj  + 2) == ':'))
     {
	psubj += 3;
	while (*psubj == ' ') psubj++;
     }
   
   return slrn_case_strcmp ((unsigned char *)subj, (unsigned char *)psubj);
}

/*}}}*/

#if SLRN_HAS_GROUPLENS
static int Update_Column_Offset = 0;
#endif

static void write_header_flags (Slrn_Header_Type *h, int row)
{
   unsigned int flags = h->flags;

#if 1
   if (Slrn_Use_Header_Numbers)
     {
	slrn_set_color (HEADER_NUMBER_COLOR);
	SLsmg_printf ("%2d", row);
	if (row > Largest_Header_Number) Largest_Header_Number = row;
     }
   else SLsmg_write_string ("  ");
#endif

   if (flags & HEADER_NTAGGED)
     {
	slrn_set_color (HIGH_SCORE_COLOR);
	SLsmg_printf ("%2d",
		      h->tag_number);
     }
   else
     {
	if ((flags & HEADER_HIGH_SCORE)
	    || ((flags & FAKE_HEADER_HIGH_SCORE)
		&& (h->child != NULL)
		&& (h->child->flags & HEADER_HIDDEN)))
	  {
	     slrn_set_color (HIGH_SCORE_COLOR);
	     SLsmg_printf ("!%c",
			   ((flags & HEADER_TAGGED) ? '*': ' '));
	  }
	else
	  {
	     slrn_set_color (0);
	     SLsmg_printf (" %c",
			   ((flags & HEADER_TAGGED) ? '*': ' '));
	  }
     }
   slrn_set_color (0);
   if (Slrn_Sorting_Mode == 0)
     {
	SLsmg_printf ("%c %6d ",
		      ((flags & HEADER_READ) ? 'D': '-'),
		      h->number);
     }
#ifdef SLRN_HAS_SORT_BY_SCORE
   else if (Slrn_Display_Score && (Slrn_Sorting_Mode & SORT_BY_SCORE))
     {
	SLsmg_printf ("%c %5d ",
		      ((flags & HEADER_READ) ? 'D': '-'),
		      ((h->child != NULL) && (h->child->flags & HEADER_HIDDEN))
		      ? h->thread_score : h->score);
     }
#endif
   else SLsmg_printf ("%c%5d:",
		      ((flags & HEADER_READ) ? 'D': '-'),
		      h->lines);
   
#if SLRN_HAS_GROUPLENS
# define UPDATE_COLUMN_OFFSET Update_Column_Offset
# define SLRN_GROUPLENS_DISPLAY_WIDTH 5
   if (Num_GroupLens_Rated != -1)
     {
	char buf [SLRN_GROUPLENS_DISPLAY_WIDTH], *b, *bmax;
	int pred = h->gl_pred;
	
	b = buf;
	bmax = b + SLRN_GROUPLENS_DISPLAY_WIDTH;
	
	if (pred < 0)
	  {
	     while (b < bmax) *b++ = ' ';
	     buf [SLRN_GROUPLENS_DISPLAY_WIDTH / 2] = '?';
	  }
	else
	  {
	     while ((pred > 0) && (b < bmax))
	       {
		  pred--;
		  *b++ = '*';
	       }
	     
	     while (b < bmax) *b++ = ' ';
	  }
	
	slrn_set_color (GROUPLENS_DISPLAY_COLOR);
	SLsmg_write_nchars (buf, SLRN_GROUPLENS_DISPLAY_WIDTH);
	slrn_set_color (0);
	
	Update_Column_Offset = SLRN_GROUPLENS_DISPLAY_WIDTH;
     }
   else Update_Column_Offset = 0;
#else
# define UPDATE_COLUMN_OFFSET 0
#endif
}

/*}}}*/

#define NEW_SCROLL
static void art_update_screen (void)
{
   Slrn_Header_Type *h;
   Slrn_Article_Line_Type *l;
   int height;
   int row;
   int c0;
   int ctrl_l_found = 0;
   
   At_End_Of_Article = NULL;
#if SLRN_HAS_SPOILERS
   Spoilers_Visible = NULL;
#endif
   if (Slrn_Full_Screen_Update) Largest_Header_Number = 0;

   /* erase last cursor */
   if (Last_Cursor_Row >= 0)
     {
	SLsmg_gotorc (Last_Cursor_Row, 0);
	if (Slrn_Use_Header_Numbers)
	  {
	     slrn_set_color (HEADER_NUMBER_COLOR);
	     SLsmg_printf ("%2d", Last_Cursor_Row);
	  }
	else SLsmg_write_string ("  ");
     }
   
   height = Header_Window_Height;
   
   if ((Top_Header != NULL)
       && (Top_Header->flags & HEADER_HIDDEN))
     Top_Header = NULL;
   
   h = Top_Header;
#ifdef NEW_SCROLL
   if (Article_Visible)
     {
	/* do not consider top and bottom lines of the header window
	 * if article one is open 
	 */
	if (height > 2) height--;
	if (Top_Header == Slrn_Current_Header)
	  h = NULL;
     }
#endif
   
   /* Find out if the header is already visible */
   
   row = 0;
   if (h != NULL)
     {
	while ((h != Slrn_Current_Header) && (row < height))
	  {
	     h = h->next;
	     if (h == NULL) break;
	     if (0 == (h->flags & HEADER_HIDDEN)) row++;
	  }
	if (row == height) h = NULL;
     }
#ifdef NEW_SCROLL
   height = Header_Window_Height;
#endif

   /* The 1 is added because of the top status line */
   if (h == Slrn_Current_Header) Last_Cursor_Row = row + 1;
   
   if (h == NULL)
     {
#ifdef NEW_SCROLL
	h = Slrn_Current_Header;
	
	if (height > 1)
	  {
	     if (h != NULL) h = h->prev;
	     while ((h != NULL) && (h->flags & HEADER_HIDDEN))
	       h = h->prev;
	     
	     if (h == NULL) h = Slrn_Current_Header;
	  }
	Top_Header = h;
#else
	h = Top_Header = Slrn_Current_Header;
#endif
	Slrn_Full_Screen_Update = 1;
	row = 0;
     }
   else h = Top_Header;
   
   SLsmg_gotorc (height + 1, 0);
   slrn_set_color (STATUS_COLOR);
   SLsmg_printf ("News Group: %s", Slrn_Current_Group_Name);
   slrn_print_percent (height + 1, 60, Header_Number, Num_Headers, height - row);
   slrn_set_color (0);
   
   
   if (Slrn_Full_Screen_Update)
     {
	for (row = 1; row <= height; row++)
	  {
	     SLsmg_gotorc (row, 0);
	     SLsmg_erase_eol ();

	     while ((h != NULL) && (h->flags & HEADER_HIDDEN))
	       h = h->next;
	     
	     if (h != NULL)
	       {
		  write_header_flags (h, row);
		  
		  if (Slrn_Show_Author > 1)
		    {
		       SLsmg_write_char ('[');
		       
		       slrn_set_color (AUTHOR_COLOR);
		       SLsmg_write_nchars (h->realname,
					   (h->realname_len > 15) ?
					   15 : h->realname_len);
		       
		       SLsmg_set_color (0);
		       SLsmg_gotorc (row, 24 + UPDATE_COLUMN_OFFSET);
		       
		       if ((h->next != NULL)
			   && (h->next->flags & HEADER_HIDDEN))
			 {
			    SLsmg_write_string ("] ");
			    slrn_set_color (THREAD_NUM_COLOR);
			    SLsmg_printf ("%2d ",
					  1 + h->num_children);
			 }
		       else
			 {
			    SLsmg_write_string ("]    ");
			    if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
			      {
				 draw_tree (h);
			      }
			 }
		       
 		       slrn_set_color (SUBJECT_COLOR);
		       
		       if ((Slrn_Show_Thread_Subject)
			   /* || (0 == h->num_children) */
			   || (h->parent == NULL)
			   || (row == 1)
			   || check_subject (h))
			 SLsmg_write_string (h->subject);
		       else
			 SLsmg_write_string (">");	/* subthread */
		    }
		  else
		    {
		       if ((h->next != NULL)
			   && (h->next->flags & HEADER_HIDDEN))
			 {
			    slrn_set_color (THREAD_NUM_COLOR);
			    SLsmg_printf (" %2d ", h->num_children + 1);
			 }
		       else
			 {
			    SLsmg_gotorc (row, 15 + UPDATE_COLUMN_OFFSET);
			    if ((h->parent != NULL) || (h->flags & FAKE_CHILDREN))
			      {
				 draw_tree (h);
			      }
			 }
		       
		       slrn_set_color (SUBJECT_COLOR);
		       SLsmg_write_string (h->subject);
		       if (Slrn_Show_Author)
			 {
			    SLsmg_gotorc (row, 50 + UPDATE_COLUMN_OFFSET);
			    slrn_set_color (AUTHOR_COLOR);
			    SLsmg_printf ("  %s", h->from);
			    SLsmg_erase_eol ();
			 }
		    }
		  
		  if (h == Slrn_Current_Header) Last_Cursor_Row = row;
		  h = h->next;
		  slrn_set_color (0);
	       }
	  }
     }
   
   if (Article_Visible &&
       (Slrn_Full_Screen_Update || (Top_Line != Current_Line)))
     {
	row = height + 2;
	height = SLtt_Screen_Rows - 2;
	
	SLsmg_gotorc (height, 0);
	slrn_set_color (STATUS_COLOR);
	if (HScroll) SLsmg_write_char ('<'); else SLsmg_write_char (' ');
	SLsmg_printf (" %d : %s", Header_Showing->number, Header_Showing->subject);
	slrn_print_percent (height, 60, Article_Line_Number, Article_Total_Lines, height - row);
	slrn_set_color (0);
	
	c0 = HScroll;
	SLsmg_set_screen_start (NULL, &c0);
	
  	Top_Line = l = Current_Line;
	
	while (row < height)
	  {
	     SLsmg_gotorc (row, 0);
	     SLsmg_erase_eol ();
	     
	     if (l != NULL)
	       {
		  if ((l->flags & HIDDEN_LINE) == 0)
		    {
		       if (l->flags & HEADER_LINE)
			 {
			    slrn_set_color (HEADER_COLOR);
			    SLsmg_write_string (l->buf);
			 }
		       else
			 {
			    if (l->flags & QUOTE_LINE)
			      slrn_set_color (QUOTE_COLOR);
			    else if (l->flags & SIGNATURE_LINE)
			      slrn_set_color (SIGNATURE_COLOR);
			    else slrn_set_color (ARTICLE_COLOR);
#if SLRN_HAS_SPOILERS
			    if (l->flags & SPOILER_LINE)
			      write_spoiler ((unsigned char *) l->buf);
			    else
#endif
			    
			    if (Do_Rot13) write_rot13 ((unsigned char *) l->buf);
			    else SLsmg_write_string (l->buf);
			 }
		       
		       row++;
		    }
#if 0
		  /* Check for ^L */
		  if ((*l->buf == 12) && (l->buf[1] == 0))
		    {
		       if (Do_Rot13 == 0) Do_Rot13 = -1;
		    }
#endif
		  l = l->next;
	       }
	     else
	       {
#if SLRN_HAS_TILDE_FEATURE
		  slrn_set_color (SLRN_TILDE_COLOR);
		  SLsmg_write_char ('~');
#endif
		  row++;
	       }
	     
	     slrn_set_color (0);
	  }
	
	if (((l == NULL) || ((l->flags & SIGNATURE_LINE) 
			     && Slrn_Sig_Is_End_Of_Article))
	    && (Slrn_Current_Header == Header_Showing)
	    && (ctrl_l_found == 0))
	  At_End_Of_Article = Slrn_Current_Header;
	
	SLsmg_set_screen_start (NULL, NULL);
     }
   
   if (Slrn_Use_Mouse) slrn_update_article_menu ();
   else
     update_top_status_line ();

   if (Slrn_Message_Present == 0) 
     {
#if SLRN_HAS_SPOILERS
	if (Spoilers_Visible != NULL)
	  slrn_message ("Spoilers visible!");
	else
#endif
	  quick_help ();
     }
   
   SLsmg_gotorc (Last_Cursor_Row, 0);
   write_header_flags (Slrn_Current_Header, Last_Cursor_Row);
   SLsmg_gotorc (Last_Cursor_Row, 0);
   slrn_set_color (CURSOR_COLOR);
   SLsmg_write_string ("->");
   slrn_set_color (0);
   
   /*   if (Slrn_Show_Author > 1) SLsmg_gotorc (Last_Cursor_Row, 7); */
   Slrn_Full_Screen_Update = 0;
}


/*}}}*/

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.