1 /* art.c
2  */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "list.h"
9 #include "trn.h"
10 #include "hash.h"
11 #include "ngdata.h"
12 #include "nntpclient.h"
13 #include "datasrc.h"
14 #include "addng.h"
15 #include "nntp.h"
16 #include "ngstuff.h"
17 #include "cache.h"
18 #include "bits.h"
19 #include "head.h"
20 #include "help.h"
21 #include "search.h"
22 #include "artio.h"
23 #include "ng.h"
24 #include "final.h"
25 #include "artstate.h"
26 #include "rcstuff.h"
27 #include "mime.h"
28 #include "term.h"
29 #include "env.h"
30 #include "util.h"
31 #include "util2.h"
32 #include "sw.h"
33 #include "kfile.h"
34 #include "decode.h"
35 #include "backpage.h"
36 #include "intrp.h"
37 #include "rthread.h"
38 #include "rt-select.h"
39 #include "rt-util.h"
40 #include "rt-wumpus.h"
41 #include "charsubst.h"
42 #ifdef SCAN_ART
43 #include "scanart.h"
44 #endif
45 #ifdef SCORE
46 #include "score.h"		/* for sc_lookahead() */
47 #endif
48 #ifdef USE_TK
49 #include "tkstuff.h"
50 #include "tktree.h"
51 #endif
52 #include "color.h"
53 #include "INTERN.h"
54 #include "art.h"
55 #include "artstate.h"			/* somebody has to do it */
56 
57 #define LINE_PTR(pos) (artbuf + (pos) - htype[PAST_HEADER].minpos)
58 #define LINE_OFFSET(ptr) ((ptr) - artbuf + htype[PAST_HEADER].minpos)
59 
60 /* page_switch() return values */
61 
62 #define PS_NORM 0
63 #define PS_ASK 1
64 #define PS_RAISE 2
65 #define PS_TOEND 3
66 
67 bool special = FALSE;		/* is next page special length? */
68 int slines = 0;			/* how long to make page when special */
69 ART_POS restart = 0;		/* if nonzero, the place where last */
70 				/* line left off on line split */
71 ART_POS alinebeg;		/* where in file current line began */
72 int more_prompt_col;		/* non-zero when the more prompt is indented */
73 
74 ART_LINE isrchline = 0;		/* last line to display */
75 #ifdef INNERSEARCH
76 COMPEX gcompex;			/* in article search pattern */
77 #endif
78 
79 bool firstpage;			/* is this the 1st page of article? */
80 bool continuation;		/* this line/header is being continued */
81 
82 void
art_init()83 art_init()
84 {
85 #ifdef INNERSEARCH
86     init_compex(&gcompex);
87 #endif
88 }
89 
90 int
do_article()91 do_article()
92 {
93     register char* s;
94     bool hide_this_line = FALSE;	/* hidden header line? */
95     bool restart_color;
96     ART_LINE linenum;			/* line # on page, 1 origin */
97 #ifdef ULSMARTS
98     bool under_lining = FALSE;		/* are we underlining a word? */
99 #endif
100     register char* bufptr = art_line;	/* pointer to input buffer */
101     register int outpos;		/* column position of output */
102     static char prompt_buf[64];		/* place to hold prompt */
103     bool notesfiles = FALSE;		/* might there be notesfiles junk? */
104     char oldmode = mode;
105     register int outputok = TRUE;
106 
107 #ifdef SUPPORT_NNTP
108     if (datasrc->flags & DF_REMOTE)
109 	artsize = raw_artsize = nntp_artsize();
110     else
111 #endif
112     {
113 	if (fstat(fileno(artfp),&filestat))	/* get article file stats */
114 	    return DA_CLEAN;
115 	if (!S_ISREG(filestat.st_mode))
116 	    return DA_NORM;
117 	artsize = raw_artsize = filestat.st_size;
118     }
119 #ifdef SCORE
120 #ifdef USE_TK
121     if (ttcl_running)
122 	ttcl_set_int("score_curvar",sc_score_art((int)art,TRUE));
123 #endif
124 #endif
125     sprintf(prompt_buf, mousebar_cnt>3? "%%sEnd of art %ld (of %ld) %%s[%%s]"
126 	: "%%sEnd of article %ld (of %ld) %%s-- what next? [%%s]",
127 	(long)art,(long)lastart);	/* format prompt string */
128     prompt = prompt_buf;
129     int_count = 0;		/* interrupt count is 0 */
130     if ((firstpage = (topline < 0)) != 0) {
131 	parseheader(art);
132 	mime_SetArticle();
133 	clear_artbuf();
134 	seekart(artbuf_seek = htype[PAST_HEADER].minpos);
135     }
136     term_scrolled = 0;
137 #ifdef USE_TK
138     if (ThreadedGroup && ttk_running)
139         ttk_draw_tree(curr_artp, 0, 0);
140 #endif
141 
142     for (;;) {			/* for each page */
143 	if (ThreadedGroup && max_tree_lines)
144 	    init_tree();	/* init tree display */
145 	assert(art == openart);
146 	if (do_fseek) {
147 	    parseheader(art);		/* make sure header is ours */
148 	    if (!*artbuf) {
149 		mime_SetArticle();
150 		artbuf_seek = htype[PAST_HEADER].minpos;
151 	    }
152 	    artpos = vrdary(artline);
153 	    if (artpos < 0)
154 		artpos = -artpos;	/* labs(), anyone? */
155 	    if (firstpage)
156 		artpos = (ART_POS)0;
157 	    if (artpos < htype[PAST_HEADER].minpos) {
158 		in_header = SOME_LINE;
159 		seekart(htype[PAST_HEADER].minpos);
160 		seekartbuf(htype[PAST_HEADER].minpos);
161 	    }
162 	    else {
163 		seekart(artbuf_seek);
164 		seekartbuf(artpos);
165 	    }
166 	    do_fseek = FALSE;
167 	    restart = 0;
168 	}
169 	linenum = 1;
170 	if (!do_hiding)
171 	    is_mime = FALSE;
172 	if (firstpage) {
173 	    if (firstline) {
174 		interp(art_line,sizeof art_line,firstline);
175 		linenum += tree_puts(art_line,linenum+topline,0);
176 	    } else {
177 		ART_NUM i;
178 		int selected, unseen;
179 
180 		selected = (curr_artp->flags & AF_SEL);
181 		unseen = article_unread(art)? 1 : 0;
182 		sprintf(art_line,"%s%s #%ld",ngname,moderated,(long)art);
183 		if (selected_only) {
184 		    i = selected_count - (unseen && selected);
185 		    sprintf(art_line+strlen(art_line)," (%ld + %ld more)",
186 			    (long)i,(long)ngptr->toread - selected_count
187 					- (!selected && unseen));
188 		}
189 		else if ((i = (ART_NUM)(ngptr->toread - unseen)) != 0
190 		       || (!ThreadedGroup && dmcount)) {
191 		    sprintf(art_line+strlen(art_line),
192 			    " (%ld more)",(long)i);
193 		}
194 		if (!ThreadedGroup && dmcount)
195 		    sprintf(art_line+strlen(art_line)-1,
196 			    " + %ld Marked to return)",(long)dmcount);
197 		linenum += tree_puts(art_line,linenum+topline,0);
198 	    }
199 	    start_header(art);
200 	    forcelast = FALSE;		/* we will have our day in court */
201 	    restart = 0;
202 	    artline = 0;		/* start counting lines */
203 	    artpos = 0;
204 	    vwtary(artline,artpos);	/* remember pos in file */
205 	}
206 	for (restart_color = 1;			/* linenum already set */
207 	  innersearch? (in_header || innermore())
208 	   : special? (linenum < slines)
209 	   : (firstpage && !in_header)? (linenum < initlines)
210 	   : (linenum < tc_LINES);
211 	     linenum++) {		/* for each line on page */
212 	    if (int_count) {	/* exit via interrupt? */
213 		newline();	/* get to left margin */
214 		int_count = 0;	/* reset interrupt count */
215 		set_mode(gmode,oldmode);
216 		special = FALSE;
217 		return DA_NORM;	/* skip out of loops */
218 	    }
219 	    if (restart) {		/* did not finish last line? */
220 		bufptr = LINE_PTR(restart);/* then start again here */
221 		restart = 0;		/* and reset the flag */
222 		continuation = 1;
223 		if (restart_color && do_hiding && !in_header)
224 		    maybe_set_color(bufptr, 1);
225 	    }
226 	    else if (in_header && *(bufptr = headbuf + artpos))
227 		continuation = *bufptr == ' ' || *bufptr == '\t';
228 	    else {
229 		if ((bufptr = readartbuf(auto_view_inline)) == NULL) {
230 		    special = FALSE;
231 		    if (innersearch)
232 			(void)innermore();
233 		    break;
234 		}
235 		if (do_hiding && !in_header)
236 		    continuation = maybe_set_color(bufptr, restart_color);
237 		else
238 		    continuation = 0;
239 	    }
240 	    alinebeg = artpos;	/* remember where we began */
241 	    restart_color = 0;
242 	    if (in_header) {
243 		hide_this_line = parseline(bufptr,do_hiding,hide_this_line);
244 		if (!in_header) {
245 		    linenum += finish_tree(linenum+topline);
246 		    end_header();
247 		    seekart(artbuf_seek);
248 		}
249 	    } else if (notesfiles && do_hiding && !continuation
250 		    && *bufptr == '#' && isupper(bufptr[1])
251 		    && bufptr[2] == ':' ) {
252 		if ((bufptr = readartbuf(auto_view_inline)) == NULL)
253 		    break;
254 		for (s = bufptr; *s && *s != '\n' && *s != '!'; s++) ;
255 		if (*s != '!')
256 		    readartbuf(auto_view_inline);
257 		mime_SetArticle();
258 		clear_artbuf();		/* exclude notesfiles droppings */
259 		artbuf_seek = htype[PAST_HEADER].minpos = tellart();
260 		hide_this_line = TRUE;	/* and do not print either */
261 		notesfiles = FALSE;
262 	    }
263 #ifdef CUSTOMLINES
264 	    if (hideline && !continuation && execute(&hide_compex,bufptr))
265 		hide_this_line = TRUE;
266 #endif
267 	    if (in_header && do_hiding && (htype[in_header].flags & HT_MAGIC)) {
268 		switch (in_header) {
269 		  case NGS_LINE:
270 		    if ((s = index(bufptr,'\n')) != NULL)
271 			*s = '\0';
272 		    hide_this_line = (index(bufptr,',') == NULL)
273 			&& strEQ(bufptr+12, ngname);
274 		    if (s != NULL)
275 			*s = '\n';
276 		    break;
277 		  case EXPIR_LINE:
278 		    if (!(htype[EXPIR_LINE].flags & HT_HIDE)) {
279 			s = bufptr + htype[EXPIR_LINE].length + 1;
280 			hide_this_line = *s != ' ' || s[1] == '\n';
281 		    }
282 		    break;
283 		 case FROM_LINE:
284 		    if ((s = index(bufptr,'\n')) != NULL
285 		     && s-bufptr < sizeof art_line)
286 			safecpy(art_line,bufptr,s-bufptr+1);
287 		    else
288 			safecpy(art_line,bufptr,sizeof art_line);
289 		    if ((s = extract_name(art_line+6)) != NULL) {
290 			strcpy(art_line+6,s);
291 			bufptr = art_line;
292 		    }
293 		    break;
294 #ifdef HAS_STRFTIME
295 		  case DATE_LINE:
296 		    if (curr_artp->date != -1) {
297 			strncpy(art_line,bufptr,6);
298 			strftime(art_line+6, (sizeof art_line)-6,
299 				 getval("LOCALTIMEFMT", LOCALTIMEFMT),
300 				 localtime(&curr_artp->date));
301 			bufptr = art_line;
302 		    }
303 		    break;
304 #endif
305 		}
306 	    }
307 	    if (in_header == SUBJ_LINE && do_hiding
308 	     && (htype[SUBJ_LINE].flags & HT_MAGIC)) { /* handle the subject */
309 		s = get_cached_line(artp, SUBJ_LINE, FALSE);
310 		if (s && continuation) {
311 		    /* continuation lines were already output */
312 		    linenum--;
313 		}
314 		else {
315 		    int length = strlen(bufptr+1);
316 		    notesfiles = instr(&bufptr[length-10]," - (nf", TRUE)!=NULL;
317 		    artline++;
318 		    if (!s)
319 			bufptr += (continuation? 0 : 9);
320 		    else
321 			bufptr = s;
322 		    /* tree_puts(, ,1) underlines subject */
323 		    linenum += tree_puts(bufptr,linenum+topline,1)-1;
324 		}
325 	    }
326 	    else if (hide_this_line && do_hiding) {   /* do not print line? */
327 		linenum--;			/* compensate for linenum++ */
328 		if (!in_header)
329 		    hide_this_line = FALSE;
330 	    }
331 	    else if (in_header) {
332 		artline++;
333 		linenum += tree_puts(bufptr,linenum+topline,0)-1;
334 	    }
335 	    else {			/* just a normal line */
336 		if (outputok && erase_each_line)
337 		    erase_line(0);
338 		if (highlight == artline) {	/* this line to be highlit? */
339 		    if (marking == STANDOUT) {
340 #ifdef NOFIREWORKS
341 			if (erase_screen)
342 			    no_sofire();
343 #endif
344 			standout();
345 		    }
346 		    else {
347 #ifdef NOFIREWORKS
348 			if (erase_screen)
349 			    no_ulfire();
350 #endif
351 			underline();
352 			carriage_return();
353 		    }
354 		    if (*bufptr == '\n')
355 			putchar(' ');
356 		}
357 		outputok = !hide_everything; /* registerize it, hopefully */
358 #ifdef CUSTOMLINES
359 		if (pagestop && !continuation && execute(&page_compex,bufptr))
360 		    linenum = 32700;
361 #endif
362 		for (outpos = 0; outpos < tc_COLS; ) { /* while line has room */
363 		    if (AT_NORM_CHAR(bufptr)) {	    /* normal char? */
364 #ifdef ULSMARTS
365 			if (*bufptr == '_') {
366 			    if (bufptr[1] == '\b') {
367 				if (outputok && !under_lining
368 				 && highlight != artline) {
369 				    under_lining++;
370 				    if (tc_UG) {
371 					if (bufptr != buf && bufptr[-1]==' ') {
372 					    outpos--;
373 					    backspace();
374 					}
375 				    }
376 				    underline();
377 				}
378 				bufptr += 2;
379 			    }
380 			}
381 			else {
382 			    if (under_lining) {
383 				under_lining = 0;
384 				un_underline();
385 				if (tc_UG) {
386 				    outpos++;
387 				    if (*bufptr == ' ')
388 					goto skip_put;
389 				}
390 			    }
391 			}
392 #endif
393 			/* handle rot-13 if wanted */
394 			if (rotate && !in_header && isalpha(*bufptr)) {
395 			    if (outputok) {
396 				if ((*bufptr & 31) <= 13)
397 				    putchar(*bufptr+13);
398 				else
399 				    putchar(*bufptr-13);
400 			    }
401 			    outpos++;
402 			}
403 			else {
404 #ifdef CHARSUBST
405 			    register int i;
406 			    i = putsubstchar(*bufptr, tc_COLS - outpos, outputok);
407 			    if (i < 0) {
408 				outpos += -i - 1;
409 				break;
410 			    }
411 			    outpos += i;
412 #else
413 			    if (outputok)
414 				putchar(*bufptr);
415 			    outpos++;
416 #endif /* CHARSUBST */
417 			}
418 			if (*tc_UC && ((highlight==artline && marking == STANDOUT)
419 #ifdef ULSMARTS
420 			    || under_lining
421 #endif
422 			    )) {
423 			    backspace();
424 			    underchar();
425 			}
426 		    skip_put:
427 			bufptr++;
428 		    }
429 		    else if (AT_NL(*bufptr) || !*bufptr) {    /* newline? */
430 #ifdef ULSMARTS
431 			if (under_lining) {
432 			    under_lining = 0;
433 			    un_underline();
434 			}
435 #endif
436 #ifdef DEBUG
437 			if (debug & DEB_INNERSRCH && outpos < tc_COLS - 6) {
438 			    standout();
439 			    printf("%4d",artline);
440 			    un_standout();
441 			}
442 #endif
443 			if (outputok)
444 			    newline();
445 			restart = 0;
446 			outpos = 1000;	/* signal normal \n */
447 		    }
448 		    else if (*bufptr == '\t') {	/* tab? */
449 			int incpos =  8 - outpos % 8;
450 			if (outputok) {
451 			    if (tc_GT)
452 				putchar(*bufptr);
453 			    else
454 				while (incpos--) putchar(' ');
455 			}
456 			bufptr++;
457 			outpos += 8 - outpos % 8;
458 		    }
459 		    else if (*bufptr == '\f') {	/* form feed? */
460 			if (outpos+2 > tc_COLS)
461 			    break;
462 			if (outputok)
463 			    fputs("^L",stdout);
464 			if (bufptr == LINE_PTR(alinebeg) && highlight != artline)
465 			    linenum = 32700;
466 			    /* how is that for a magic number? */
467 			bufptr++;
468 			outpos += 2;
469 		    }
470 		    else {		/* other control char */
471 			if (dont_filter_control) {
472 			    if (outputok)
473 				putchar(*bufptr);
474 			    outpos++;
475 			}
476 			else if (*bufptr != '\r' || bufptr[1] != '\n') {
477 			    if (outpos+2 > tc_COLS)
478 				break;
479 			    if (outputok) {
480 				putchar('^');
481 				if (highlight == artline && *tc_UC
482 				 && marking == STANDOUT) {
483 				    backspace();
484 				    underchar();
485 				    putchar((*bufptr & 0x7F) ^ 0x40);
486 				    backspace();
487 				    underchar();
488 				}
489 				else
490 				    putchar((*bufptr & 0x7F) ^ 0x40);
491 			    }
492 			    outpos += 2;
493 			}
494 			bufptr++;
495 		    }
496 
497 		} /* end of column loop */
498 
499 		if (outpos < 1000) {	/* did line overflow? */
500 		    restart = LINE_OFFSET(bufptr);/* restart here next time */
501 		    if (outputok) {
502 			if (!tc_AM || tc_XN || outpos < tc_COLS)
503 			    newline();
504 			else
505 			    term_line++;
506 		    }
507 		    if (AT_NL(*bufptr))		/* skip the newline */
508 			restart = 0;
509 		}
510 
511 		/* handle normal end of output line formalities */
512 
513 		if (highlight == artline) {
514 					/* were we highlighting line? */
515 		    if (marking == STANDOUT)
516 			un_standout();
517 		    else
518 			un_underline();
519 		    carriage_return();
520 		    highlight = -1;	/* no more we are */
521 		    /* in case terminal highlighted rest of line earlier */
522 		    /* when we did an eol with highlight turned on: */
523 		    if (erase_each_line)
524 			erase_eol();
525 		}
526 		artline++;	/* count the line just printed */
527 		if (artline - tc_LINES + 1 > topline)
528 			    /* did we just scroll top line off? */
529 		    topline = artline - tc_LINES + 1;
530 			    /* then recompute top line # */
531 	    }
532 
533 	    /* determine actual position in file */
534 
535 	    if (restart)	/* stranded somewhere in the buffer? */
536 		artpos += restart - alinebeg;
537 	    else if (in_header)
538 		artpos = index(headbuf+artpos,'\n') - headbuf + 1;
539 	    else
540 		artpos = artbuf_pos + htype[PAST_HEADER].minpos;
541 	    vwtary(artline,artpos);	/* remember pos in file */
542 	} /* end of line loop */
543 
544 #ifdef INNERSEARCH
545 	innersearch = 0;
546 	if (hide_everything) {
547 	    *buf = hide_everything;
548 	    hide_everything = 0;
549 	    goto fake_command;
550 	}
551 #endif
552 	if (linenum >= 32700)	/* did last line have formfeed? */
553 	    vwtary(artline-1,-vrdary(artline-1));
554 				/* remember by negating pos in file */
555 
556 	special = FALSE;	/* end of page, so reset page length */
557 	firstpage = FALSE;	/* and say it is not 1st time thru */
558 	highlight = -1;
559 
560 	/* extra loop bombout */
561 
562 #ifdef SUPPORT_NNTP
563 	if (artsize < 0 && (raw_artsize = nntp_artsize()) >= 0)
564 	    artsize = raw_artsize-artbuf_seek+artbuf_len+htype[PAST_HEADER].minpos;
565 recheck_pager:
566 	if (do_hiding && artbuf_pos == artbuf_len) {
567 	    /* If we're filtering we need to figure out if any
568 	     * remaining text is going to vanish or not. */
569 	    long seekpos = artbuf_pos + htype[PAST_HEADER].minpos;
570 	    readartbuf(FALSE);
571 	    seekartbuf(seekpos);
572 	}
573 #endif
574 	if (artpos == artsize) {/* did we just now reach EOF? */
575 	    color_default();
576 	    set_mode(gmode,oldmode);
577 	    return DA_NORM;	/* avoid --MORE--(100%) */
578 	}
579 
580 /* not done with this article, so pretend we are a pager */
581 
582 reask_pager:
583 	if (term_line >= tc_LINES) {
584 	    term_scrolled += term_line - tc_LINES + 1;
585 	    term_line = tc_LINES-1;
586 	}
587 	more_prompt_col = term_col;
588 
589 	unflush_output();	/* disable any ^O in effect */
590  	maybe_eol();
591 	color_default();
592 #ifdef SUPPORT_NNTP
593 	if (artsize < 0)
594 	    strcpy(cmd_buf,"?");
595 	else
596 #endif
597 	    sprintf(cmd_buf,"%ld",(long)(artpos*100/artsize));
598 #ifdef CHARSUBST
599 	sprintf(buf,"%s--MORE--(%s%%)",current_charsubst(),cmd_buf);
600 #else
601 	sprintf(buf,"--MORE--(%s%%)",cmd_buf);
602 #endif
603 	outpos = term_col + strlen(buf);
604 	draw_mousebar(tc_COLS - (term_line == tc_LINES-1? outpos+5 : 0), 1);
605 	color_string(COLOR_MORE,buf);
606 	fflush(stdout);
607 	term_col = outpos;
608 	eat_typeahead();
609 #ifdef DEBUG
610 	if (debug & DEB_CHECKPOINTING) {
611 	    printf("(%d %d %d)",checkcount,linenum,artline);
612 	    fflush(stdout);
613 	}
614 #endif
615 	if (checkcount >= docheckwhen && linenum == tc_LINES
616 	 && (artline > 40 || checkcount >= docheckwhen+10)) {
617 			    /* while he is reading a whole page */
618 			    /* in an article he is interested in */
619 	    checkcount = 0;
620 	    checkpoint_newsrcs();	/* update all newsrcs */
621 #ifdef KILLFILES
622 	    update_thread_kfile();
623 #endif
624 	}
625 	cache_until_key();
626 #ifdef SUPPORT_NNTP
627 	if (artsize < 0 && (raw_artsize = nntp_artsize()) >= 0) {
628 	    artsize = raw_artsize-artbuf_seek+artbuf_len+htype[PAST_HEADER].minpos;
629 	    goto_xy(more_prompt_col,term_line);
630 	    goto recheck_pager;
631 	}
632 #endif
633 	set_mode(gmode,'p');
634 	getcmd(buf);
635 	if (errno) {
636 	    if (tc_LINES < 100 && !int_count)
637 		*buf = '\f';/* on CONT fake up refresh */
638 	    else {
639 		*buf = 'q';	/* on INTR or paper just quit */
640 	    }
641 	}
642 	erase_line(erase_screen && erase_each_line);
643 
644     fake_command:		/* used by innersearch */
645 	color_default();
646 	output_chase_phrase = TRUE;
647 
648 	/* parse and process pager command */
649 
650 	if (mousebar_cnt)
651 	    clear_rest();
652 	switch (page_switch()) {
653 	  case PS_ASK:	/* reprompt "--MORE--..." */
654 	    goto reask_pager;
655 	  case PS_RAISE:	/* reparse on article level */
656 	    set_mode(gmode,oldmode);
657 	    return DA_RAISE;
658 	  case PS_TOEND:	/* fast pager loop exit */
659 	    set_mode(gmode,oldmode);
660 	    return DA_TOEND;
661 	  case PS_NORM:		/* display more article */
662 	    break;
663 	}
664     } /* end of page loop */
665 }
666 
667 int
maybe_set_color(cp,backsearch)668 maybe_set_color(cp, backsearch)
669 char* cp;
670 bool_int backsearch;
671 {
672     register char ch = (cp == artbuf || cp == art_line? 0 : cp[-1]);
673     if (ch == '\001')
674 	color_object(COLOR_MIMEDESC, 0);
675     else if (ch == '\002')
676 	color_object(COLOR_MIMESEP, 0);
677     else if (ch == WRAPPED_NL) {
678 	if (backsearch) {
679 	    while (cp > artbuf && cp[-1] != '\n') cp--;
680 	    maybe_set_color(cp, 0);
681 	}
682 	return 1;
683     }
684     else {
685 	while (*cp == ' ' || *cp == '\t') cp++;
686 	if (index(">}]#!:|", *cp))
687 	    color_object(COLOR_CITEDTEXT, 0);
688 	else
689 	    color_object(COLOR_BODYTEXT, 0);
690     }
691     return 0;
692 }
693 
694 /* process pager commands */
695 
696 int
page_switch()697 page_switch()
698 {
699     register char* s;
700 
701     switch (*buf) {
702       case '!':			/* shell escape */
703 	escapade();
704 	return PS_ASK;
705 #ifdef INNERSEARCH
706       case Ctl('i'): {
707 	ART_LINE i = artline;
708 	ART_POS pos;
709 	gline = 3;
710 	s = LINE_PTR(alinebeg);
711 	while (AT_NL(*s) && i >= topline) {
712 	    pos = vrdary(--i);
713 	    if (pos < 0)
714 		pos = -pos;
715 	    if (pos < htype[PAST_HEADER].minpos)
716 		break;
717 	    seekartbuf(pos);
718 	    if ((s = readartbuf(FALSE)) == NULL) {
719 		s = LINE_PTR(alinebeg);
720 		break;
721 	    }
722 	}
723 	sprintf(cmd_buf,"^[^%c\n]",*s);
724 	compile(&gcompex,cmd_buf,TRUE,TRUE);
725 	goto caseG;
726       }
727       case Ctl('g'):
728 	gline = 3;
729 	compile(&gcompex,"^Subject:",TRUE,TRUE);
730 	goto caseG;
731       case 'g':		/* in-article search */
732 	if (!finish_command(FALSE))/* get rest of command */
733 	    return PS_ASK;
734 	s = buf+1;
735 	if (isspace(*s)) s++;
736 	if ((s = compile(&gcompex,s,TRUE,TRUE)) != NULL) {
737 			    /* compile regular expression */
738 	    printf("\n%s\n",s) FLUSH;
739 	    termdown(2);
740 	    return PS_ASK;
741 	}
742 	erase_line(0);	/* erase the prompt */
743 	/* FALL THROUGH */
744       caseG:
745       case 'G': {
746 	ART_POS start_where;
747 	bool success;
748 	char* nlptr;
749 	char ch;
750 
751 	if (gline < 0 || gline > tc_LINES-2)
752 	    gline = tc_LINES-2;
753 #ifdef DEBUG
754 	if (debug & DEB_INNERSRCH) {
755 	    printf("Start here? %d  >=? %d\n",topline + gline + 1,artline)
756 	      FLUSH;
757 	    termdown(1);
758 	}
759 #endif
760 	if (*buf == Ctl('i') || topline+gline+1 >= artline)
761 	    start_where = artpos;
762 			/* in case we had a line wrap */
763 	else {
764 	    start_where = vrdary(topline+gline+1);
765 	    if (start_where < 0)
766 		start_where = -start_where;
767 	}
768 	if (start_where < htype[PAST_HEADER].minpos)
769 	    start_where = htype[PAST_HEADER].minpos;
770 	seekartbuf(start_where);
771 	innerlight = 0;
772 	innersearch = 0; /* assume not found */
773 	while ((s = readartbuf(FALSE)) != NULL) {
774 	    if ((nlptr = index(s,'\n')) != NULL) {
775 		ch = *++nlptr;
776 		*nlptr = '\0';
777 	    }
778 #ifdef DEBUG
779 	    if (debug & DEB_INNERSRCH)
780 		printf("Test %s\n",s) FLUSH;
781 #endif
782 	    success = execute(&gcompex,s) != NULL;
783 	    if (nlptr)
784 		*nlptr = ch;
785 	    if (success) {
786 		innersearch = artbuf_pos + htype[PAST_HEADER].minpos;
787 		break;
788 	    }
789 	}
790 	if (!innersearch) {
791 	    seekartbuf(artpos);
792 	    fputs("(Not found)",stdout) FLUSH;
793 	    term_col = 11;
794 	    return PS_ASK;
795 	}
796 #ifdef DEBUG
797 	if (debug & DEB_INNERSRCH) {
798 	    printf("On page? %ld <=? %ld\n",(long)innersearch,(long)artpos)
799 	      FLUSH;
800 	    termdown(1);
801 	}
802 #endif
803 	if (innersearch <= artpos) {	/* already on page? */
804 	    if (innersearch < artpos) {
805 		artline = topline+1;
806 		while (vrdary(artline) < innersearch)
807 		    artline++;
808 	    }
809 	    highlight = artline - 1;
810 #ifdef DEBUG
811 	    if (debug & DEB_INNERSRCH) {
812 		printf("@ %d\n",highlight) FLUSH;
813 		termdown(1);
814 	    }
815 #endif
816 	    topline = highlight - gline;
817 	    if (topline < -1)
818 		topline = -1;
819 	    *buf = '\f';		/* fake up a refresh */
820 	    innersearch = 0;
821 	    return page_switch();
822 	}
823 	else {				/* who knows how many lines it is? */
824 	    do_fseek = TRUE;
825 	    hide_everything = '\f';
826 	}
827 	return PS_NORM;
828       }
829 #else
830       case 'g': case 'G': case Ctl('g'):
831 	notincl("g");
832 	return PS_ASK;
833 #endif
834       case '\n':		/* one line down */
835       case '\r':
836 	special = TRUE;
837 	slines = 2;
838 	return PS_NORM;
839       case 'X':
840 	rotate = !rotate;
841 	/* FALL THROUGH */
842       case 'l':
843       case '\f':		/* refresh screen */
844       refresh_screen:
845 #ifdef DEBUG
846 	if (debug & DEB_INNERSRCH) {
847 	    printf("Topline = %d",topline) FLUSH;
848 	    fgets(buf, sizeof buf, stdin);
849 	}
850 #endif
851 	clear();
852 	do_fseek = TRUE;
853 	artline = topline;
854 	if (artline < 0)
855 	    artline = 0;
856 	firstpage = (topline < 0);
857 	return PS_NORM;
858 #ifdef INNERSEARCH
859       case Ctl('e'):
860 #ifdef SUPPORT_NNTP
861 	if (artsize < 0) {
862 	    nntp_finishbody(FB_OUTPUT);
863 	    raw_artsize = nntp_artsize();
864 	    artsize = raw_artsize-artbuf_seek+artbuf_len+htype[PAST_HEADER].minpos;
865 	}
866 #endif
867 	if (do_hiding) {
868 	    seekartbuf(artsize);
869 	    seekartbuf(artpos);
870 	}
871 	topline = artline;
872 	innerlight = artline - 1;
873 	innersearch = artsize;
874 	gline = 0;
875 	hide_everything = 'b';
876 	return PS_NORM;
877 #endif
878       case 'B':		/* one line up */
879 	if (topline < 0)
880 	    break;
881 	if (*tc_IL && *tc_HO) {
882 	    ART_POS pos;
883 	    home_cursor();
884 	    insert_line();
885 	    carriage_return();
886 	    pos = vrdary(topline-1);
887 	    if (pos < 0)
888 		pos = -pos;
889 	    if (pos >= htype[PAST_HEADER].minpos) {
890 		seekartbuf(pos);
891 		if ((s = readartbuf(FALSE)) != NULL) {
892 		    artpos = vrdary(topline);
893 		    if (artpos < 0)
894 			artpos = -artpos;
895 		    maybe_set_color(s, 1);
896 		    for (pos = artpos - pos; pos-- && !AT_NL(*s); s++)
897 			putchar(*s);
898 		    color_default();
899 		    putchar('\n') FLUSH;
900 		    topline--;
901 		    artpos = vrdary(--artline);
902 		    if (artpos < 0)
903 			artpos = -artpos;
904 		    seekartbuf(artpos);
905 		    alinebeg = vrdary(artline-1);
906 		    if (alinebeg < 0)
907 			alinebeg = -alinebeg;
908 		    goto_xy(0,artline-topline);
909 		    erase_line(0);
910 		    return PS_ASK;
911 		}
912 	    }
913 	}
914 	/* FALL THROUGH */
915       case 'b':
916       case Ctl('b'): {	/* back up a page */
917 	ART_LINE target;
918 
919 	if (erase_each_line)
920 	    home_cursor();
921 	else
922 	    clear();
923 
924 	do_fseek = TRUE;	/* reposition article file */
925 	if (*buf == 'B')
926 	    target = topline - 1;
927 	else {
928 	    target = topline - (tc_LINES - 2);
929 	    if (marking && (marking_areas & BACKPAGE_MARKING))
930 		highlight = topline;
931 	}
932 	artline = topline;
933 	if (artline >= 0) do {
934 	    artline--;
935 	} while(artline >= 0 && artline > target && vrdary(artline-1) >= 0);
936 	topline = artline;	/* remember top line of screen */
937 				/*  (line # within article file) */
938 	if (artline < 0)
939 	    artline = 0;
940 	firstpage = (topline < 0);
941 	return PS_NORM;
942     }
943       case 'H':		/* help */
944 	help_page();
945 	return PS_ASK;
946       case 't':		/* output thread data */
947 	page_line = 1;
948 	entire_tree(curr_artp);
949 	return PS_ASK;
950       case '_':
951 	if (!finish_dblchar())
952 	    return PS_ASK;
953 	switch (buf[1] & 0177) {
954 #ifdef CHARSUBST
955 	  case 'C':
956 	    if (!*(++charsubst))
957 		charsubst = charsets;
958 	    goto refresh_screen;
959 #endif
960 	  default:
961 	    break;
962 	}
963 	goto leave_pager;
964       case '\0':		/* treat break as 'n' */
965 	*buf = 'n';
966 	/* FALL THROUGH */
967       case 'a': case 'A':
968       case 'e':
969       case 'k': case 'K': case 'J':
970       case 'n': case 'N': case Ctl('n'):
971 		case 'F':
972 		case 'R':
973       case 's': case 'S':
974 		case 'T':
975       case 'u':
976       case 'w':	case 'W':
977       case '|':
978 	mark_as_read(artp);	/* mark article as read */
979 	/* FALL THROUGH */
980       case 'U': case ',':
981       case '<': case '>':
982       case '[': case ']':
983       case '{': case '}':
984       case '(': case ')':
985       case ':':
986       case '+':
987       case Ctl('v'):		/* verify crypto signature */
988 #ifdef SCAN_ART
989       case ';':			/* enter article scan mode */
990 #endif
991 #ifdef SCORE
992       case '"':			/* append to local scorefile */
993       case '\'':		/* score command */
994 #endif
995       case '#':
996       case '$':
997       case '&':
998       case '-':
999       case '.':
1000       case '/':
1001       case '1': case '2': case '3': case '4': case '5':
1002       case '6': case '7': case '8': case '9':
1003       case '=':
1004       case '?':
1005       case 'c': case 'C':
1006 #ifdef DEBUG
1007 		case 'D':
1008 #endif
1009       case 'f':		  case Ctl('f'):
1010       case 'h':
1011       case 'j':
1012 			  case Ctl('k'):
1013       case 'm': case 'M':
1014       case 'p': case 'P': case Ctl('p'):
1015       case '`': case 'Q':
1016       case 'r':		  case Ctl('r'):
1017       case 'v':
1018       case 'x':		  case Ctl('x'):
1019 		case 'Y':
1020       case 'z': case 'Z':
1021       case '^':		  case Ctl('^'):
1022 	       case '\b': case '\177':
1023 leave_pager:
1024 	reread = FALSE;
1025 	if (index("nNpP\016\020",*buf) == NULL
1026 	 && index("wWsSe:!&|/?123456789.",*buf) != NULL) {
1027 	    setdfltcmd();
1028 	    color_object(COLOR_CMD, 1);
1029 	    interpsearch(cmd_buf, sizeof cmd_buf, mailcall, buf);
1030 	    printf(prompt,cmd_buf,
1031 #ifdef CHARSUBST
1032 		   current_charsubst(),
1033 #else
1034 		   nullstr,
1035 #endif
1036 		   dfltcmd);	/* print prompt, whatever it is */
1037 	    color_pop();	/* of COLOR_CMD */
1038 	    putchar(' ');
1039 	    fflush(stdout);
1040 	}
1041 	return PS_RAISE;	/* and pretend we were at end */
1042       case 'd':		/* half page */
1043       case Ctl('d'):
1044 	special = TRUE;
1045 	slines = tc_LINES / 2 + 1;
1046 	/* no divide-by-zero, thank you */
1047 	if (tc_LINES > 2 && (tc_LINES & 1) && artline % (tc_LINES-2) >= tc_LINES/2 - 1)
1048 	    slines++;
1049 	goto go_forward;
1050       case 'y':
1051       case ' ':	/* continue current article */
1052 	if (erase_screen) {	/* -e? */
1053 	    if (erase_each_line)
1054 		home_cursor();
1055 	    else
1056 		clear();	/* clear screen */
1057 	    fflush(stdout);
1058 	}
1059 	else {
1060 	    special = TRUE;
1061 	    slines = tc_LINES;
1062 	}
1063       go_forward:
1064 	if (*LINE_PTR(alinebeg) != '\f'
1065 #ifdef CUSTOMLINES
1066 	  && (!pagestop || continuation || !execute(&page_compex,LINE_PTR(alinebeg)))
1067 #endif
1068 	  ) {
1069 	    if (!special
1070 	     || (marking && (*buf!='d' || (marking_areas&HALFPAGE_MARKING)))) {
1071 		restart = alinebeg;
1072 		artline--;	 /* restart this line */
1073 		artpos = alinebeg;
1074 		if (special)
1075 		    up_line();
1076 		else
1077 		    topline = artline;
1078 		if (marking)
1079 		    highlight = artline;
1080 	    }
1081 	    else
1082 		slines--;
1083 	}
1084 	return PS_NORM;
1085       case 'i':
1086 	if ((auto_view_inline = !auto_view_inline) != 0)
1087 	    first_view = 0;
1088 	printf("\nAuto-View inlined mime is %s\n", auto_view_inline? "on" : "off");
1089 	termdown(2);
1090 	break;
1091       case 'q':	/* quit this article? */
1092 	return PS_TOEND;
1093       default:
1094 	fputs(hforhelp,stdout) FLUSH;
1095 	termdown(1);
1096 	settle_down();
1097 	return PS_ASK;
1098     }
1099     return PS_ASK;
1100 }
1101 
1102 bool
innermore()1103 innermore()
1104 {
1105     if (artpos < innersearch) {		/* not even on page yet? */
1106 #ifdef DEBUG
1107 	if (debug & DEB_INNERSRCH)
1108 	    printf("Not on page %ld < %ld\n",(long)artpos,(long)innersearch)
1109 	      FLUSH;
1110 #endif
1111 	return TRUE;
1112     }
1113     if (artpos == innersearch) {	/* just got onto page? */
1114 	isrchline = artline;		/* remember first line after */
1115 	if (innerlight)
1116 	    highlight = innerlight;
1117 	else
1118 	    highlight = artline - 1;
1119 #ifdef DEBUG
1120 	if (debug & DEB_INNERSRCH) {
1121 	    printf("There it is %ld = %ld, %d @ %d\n",(long)artpos,
1122 		(long)innersearch,hide_everything,highlight) FLUSH;
1123 	    termdown(1);
1124 	}
1125 #endif
1126 	if (hide_everything) {		/* forced refresh? */
1127 	    topline = artline - gline - 1;
1128 	    if (topline < -1)
1129 		topline = -1;
1130 	    return FALSE;		/* let refresh do it all */
1131 	}
1132     }
1133 #ifdef DEBUG
1134     if (debug & DEB_INNERSRCH) {
1135 	printf("Not far enough? %d <? %d + %d\n",artline,isrchline,gline)
1136 	  FLUSH;
1137 	termdown(1);
1138     }
1139 #endif
1140     if (artline < isrchline + gline)
1141 	return TRUE;
1142     return FALSE;
1143 }
1144 
1145 /* On click:
1146  *    btn = 0 (left), 1 (middle), or 2 (right) + 4 if double-clicked;
1147  *    x = 0 to tc_COLS-1; y = 0 to tc_LINES-1;
1148  *    btn_clk = 0, 1, or 2 (no 4); x_clk = x; y_clk = y.
1149  * On release:
1150  *    btn = 3; x = released x; y = released y;
1151  *    btn_clk = click's 0, 1, or 2; x_clk = clicked x; y_clk = clicked y.
1152  */
1153 void
pager_mouse(btn,x,y,btn_clk,x_clk,y_clk)1154 pager_mouse(btn, x,y, btn_clk, x_clk,y_clk)
1155 int btn;
1156 int x, y;
1157 int btn_clk;
1158 int x_clk, y_clk;
1159 {
1160     ARTICLE* ap;
1161 
1162     if (check_mousebar(btn, x,y, btn_clk, x_clk,y_clk))
1163 	return;
1164 
1165     if (btn != 3)
1166 	return;
1167 
1168     ap = get_tree_artp(x_clk,y_clk+topline+1+term_scrolled);
1169     if (ap && ap != get_tree_artp(x,y+topline+1+term_scrolled))
1170 	return;
1171 
1172     switch (btn_clk) {
1173       case 0:
1174 	if (ap) {
1175 	    if (ap == artp)
1176 		return;
1177 	    artp = ap;
1178 	    art = article_num(ap);
1179 	    reread = TRUE;
1180 	    pushchar(Ctl('r'));
1181 	}
1182 	else if (y > tc_LINES/2)
1183 	    pushchar(' ');
1184 	else if (topline != -1)
1185 	    pushchar('b');
1186 	break;
1187       case 1:
1188 	if (ap) {
1189 	    select_subthread(ap, 0);
1190 	    special = TRUE;
1191 	    slines = 1;
1192 	    pushchar(Ctl('r'));
1193 	}
1194 	else if (y > tc_LINES/2)
1195 	    pushchar('\n');
1196 	else if (topline != -1)
1197 	    pushchar('B');
1198 	break;
1199       case 2:
1200 	if (ap) {
1201 	    kill_subthread(ap, 0);
1202 	    special = TRUE;
1203 	    slines = 1;
1204 	    pushchar(Ctl('r'));
1205 	}
1206 	else if (y > tc_LINES/2)
1207 	    pushchar('n');
1208 	else
1209 	    pushchar(Ctl('r'));
1210 	break;
1211     }
1212 }
1213