pager.c 75.7 KB
Newer Older
Thomas Roessler's avatar
Thomas Roessler committed
1
/*
2
 * Copyright (C) 1996-2002,2007,2010,2012-2013 Michael R. Elkins <me@mutt.org>
3
 *
Thomas Roessler's avatar
Thomas Roessler committed
4 5 6 7
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
8
 *
Thomas Roessler's avatar
Thomas Roessler committed
9 10 11 12
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
13
 *
Thomas Roessler's avatar
Thomas Roessler committed
14 15
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
16
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17
 */
Thomas Roessler's avatar
Thomas Roessler committed
18

19 20 21 22
#if HAVE_CONFIG_H
# include "config.h"
#endif

Thomas Roessler's avatar
Thomas Roessler committed
23 24 25 26 27
#include "mutt.h"
#include "mutt_curses.h"
#include "mutt_regex.h"
#include "keymap.h"
#include "mutt_menu.h"
Thomas Roessler's avatar
Thomas Roessler committed
28
#include "mapping.h"
Thomas Roessler's avatar
Thomas Roessler committed
29 30
#include "pager.h"
#include "attach.h"
31
#include "mbyte.h"
32
#include "sort.h"
33 34 35
#ifdef USE_SIDEBAR
#include "sidebar.h"
#endif
Thomas Roessler's avatar
Thomas Roessler committed
36

37
#include "mutt_crypt.h"
Thomas Roessler's avatar
Thomas Roessler committed
38 39 40 41 42 43

#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
Thomas Roessler's avatar
Thomas Roessler committed
44
#include <errno.h>
Thomas Roessler's avatar
Thomas Roessler committed
45 46 47 48

#define ISHEADER(x) ((x) == MT_COLOR_HEADER || (x) == MT_COLOR_HDEFAULT)

#define IsAttach(x) (x && (x)->bdy)
49 50 51 52
#define IsRecvAttach(x) (x && (x)->bdy && (x)->fp)
#define IsSendAttach(x) (x && (x)->bdy && !(x)->fp)
#define IsMsgAttach(x) (x && (x)->fp && (x)->bdy && (x)->bdy->hdr)
#define IsHeader(x) (x && (x)->hdr && !(x)->bdy)
Thomas Roessler's avatar
Thomas Roessler committed
53

Thomas Roessler's avatar
Thomas Roessler committed
54 55 56
static const char *Not_available_in_this_menu = N_("Not available in this menu.");
static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
static const char *Function_not_permitted_in_attach_message_mode = N_("Function not permitted in attach-message mode.");
57

58 59 60 61
/* hack to return to position when returning from index to same message */
static int TopLine = 0;
static HEADER *OldHdr = NULL;

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
#define CHECK_MODE(x)                           \
  if (!(x))                                     \
  {                                             \
    mutt_flushinp ();                           \
    mutt_error _(Not_available_in_this_menu);   \
    break;                                      \
  }

#define CHECK_READONLY                          \
  if (Context->readonly)                        \
  {                                             \
    mutt_flushinp ();                           \
    mutt_error _(Mailbox_is_read_only);         \
    break;                                      \
  }

#define CHECK_ATTACH                                                    \
  if(option(OPTATTACHMSG))                                              \
  {                                                                     \
    mutt_flushinp ();                                                   \
    mutt_error _(Function_not_permitted_in_attach_message_mode);        \
    break;                                                              \
  }

#define CHECK_ACL(aclbit,action)                                        \
  if (!mutt_bit_isset(Context->rights,aclbit))                          \
  {                                                                     \
    mutt_flushinp();                                                    \
    /* L10N: %s is one of the CHECK_ACL entries below. */               \
    mutt_error (_("%s: Operation not permitted by ACL"), action);       \
    break;                                                              \
  }
94

Thomas Roessler's avatar
Thomas Roessler committed
95 96 97
struct q_class_t
{
  int length;
98
  int index;
Thomas Roessler's avatar
Thomas Roessler committed
99 100 101 102 103 104 105 106 107
  int color;
  char *prefix;
  struct q_class_t *next, *prev;
  struct q_class_t *down, *up;
};

struct syntax_t
{
  int color;
108 109
  int first;
  int last;
Thomas Roessler's avatar
Thomas Roessler committed
110 111 112 113
};

struct line_t
{
114
  LOFF_T offset;
Thomas Roessler's avatar
Thomas Roessler committed
115 116 117 118 119 120 121
  short type;
  short continuation;
  short chunks;
  short search_cnt;
  struct syntax_t *syntax;
  struct syntax_t *search;
  struct q_class_t *quote;
122
  unsigned int is_cont_hdr; /* this line is a continuation of the previous header line */
Thomas Roessler's avatar
Thomas Roessler committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
};

#define ANSI_OFF       (1<<0)
#define ANSI_BLINK     (1<<1)
#define ANSI_BOLD      (1<<2)
#define ANSI_UNDERLINE (1<<3)
#define ANSI_REVERSE   (1<<4)
#define ANSI_COLOR     (1<<5)

typedef struct _ansi_attr {
  int attr;
  int fg;
  int bg;
  int pair;
} ansi_attr;

static short InHelp = 0;

141 142 143 144 145 146 147 148
#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)
static struct resize {
  int line;
  int SearchCompiled;
  int SearchBack;
} *Resize = NULL;
#endif

Thomas Roessler's avatar
Thomas Roessler committed
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
#define NumSigLines 4

static int check_sig (const char *s, struct line_t *info, int n)
{
  int count = 0;

  while (n > 0 && count <= NumSigLines)
  {
    if (info[n].type != MT_COLOR_SIGNATURE)
      break;
    count++;
    n--;
  }

  if (count == 0)
    return (-1);

  if (count > NumSigLines)
  {
    /* check for a blank line */
    while (*s)
    {
      if (!ISSPACE (*s))
	return 0;
      s++;
    }

    return (-1);
  }

  return (0);
}

182 183 184 185 186 187 188 189 190 191 192 193 194
static int
comp_syntax_t (const void *m1, const void *m2)
{
  const int *cnt = (const int *)m1;
  const struct syntax_t *stx = (const struct syntax_t *)m2;

  if (*cnt < stx->first)
    return -1;
  if (*cnt >= stx->last)
    return 1;
  return 0;
}

Thomas Roessler's avatar
Thomas Roessler committed
195 196
static void
resolve_color (struct line_t *lineInfo, int n, int cnt, int flags, int special,
197
               ansi_attr *a)
Thomas Roessler's avatar
Thomas Roessler committed
198
{
199
  int def_color;		/* color without syntax highlight */
Thomas Roessler's avatar
Thomas Roessler committed
200 201
  int color;			/* final color */
  static int last_color;	/* last color set */
202 203
  int search = 0, m;
  struct syntax_t *matching_chunk;
Thomas Roessler's avatar
Thomas Roessler committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

  if (!cnt)
    last_color = -1;		/* force attrset() */

  if (lineInfo[n].continuation)
  {
    if (!cnt && option (OPTMARKERS))
    {
      SETCOLOR (MT_COLOR_MARKERS);
      addch ('+');
      last_color = ColorDefs[MT_COLOR_MARKERS];
    }
    m = (lineInfo[n].syntax)[0].first;
    cnt += (lineInfo[n].syntax)[0].last;
  }
  else
    m = n;
221
  if (!(flags & MUTT_SHOWCOLOR))
Thomas Roessler's avatar
Thomas Roessler committed
222 223 224 225 226 227
    def_color = ColorDefs[MT_COLOR_NORMAL];
  else if (lineInfo[m].type == MT_COLOR_HEADER)
    def_color = (lineInfo[m].syntax)[0].color;
  else
    def_color = ColorDefs[lineInfo[m].type];

228
  if ((flags & MUTT_SHOWCOLOR) && lineInfo[m].type == MT_COLOR_QUOTED)
Thomas Roessler's avatar
Thomas Roessler committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
  {
    struct q_class_t *class = lineInfo[m].quote;

    if (class)
    {
      def_color = class->color;

      while (class && class->length > cnt)
      {
	def_color = class->color;
	class = class->up;
      }
    }
  }

  color = def_color;
245
  if ((flags & MUTT_SHOWCOLOR) && lineInfo[m].chunks)
Thomas Roessler's avatar
Thomas Roessler committed
246
  {
247 248 249 250 251 252
    matching_chunk = bsearch (&cnt, lineInfo[m].syntax, lineInfo[m].chunks,
                              sizeof(struct syntax_t), comp_syntax_t);
    if (matching_chunk &&
        (cnt >= matching_chunk->first) &&
        (cnt < matching_chunk->last))
      color = matching_chunk->color;
Thomas Roessler's avatar
Thomas Roessler committed
253 254
  }

255
  if ((flags & MUTT_SEARCH) && lineInfo[m].search_cnt)
Thomas Roessler's avatar
Thomas Roessler committed
256
  {
257 258 259 260 261
    matching_chunk = bsearch (&cnt, lineInfo[m].search, lineInfo[m].search_cnt,
                              sizeof(struct syntax_t), comp_syntax_t);
    if (matching_chunk &&
        (cnt >= matching_chunk->first) &&
        (cnt < matching_chunk->last))
Thomas Roessler's avatar
Thomas Roessler committed
262
    {
263 264
      color = ColorDefs[MT_COLOR_SEARCH];
      search = 1;
Thomas Roessler's avatar
Thomas Roessler committed
265 266 267 268 269 270
    }
  }

  /* handle "special" bold & underlined characters */
  if (special || a->attr)
  {
271
#ifdef HAVE_COLOR
Thomas Roessler's avatar
Thomas Roessler committed
272
    if ((a->attr & ANSI_COLOR))
273 274
    {
      if (a->pair == -1)
Thomas Roessler's avatar
Thomas Roessler committed
275
	a->pair = mutt_alloc_color (a->fg, a->bg);
276 277
      color = a->pair;
      if (a->attr & ANSI_BOLD)
278
        color |= A_BOLD;
279 280 281 282
    }
    else
#endif
      if ((special & A_BOLD) || (a->attr & ANSI_BOLD))
283 284 285 286 287 288
      {
        if (ColorDefs[MT_COLOR_BOLD] && !search)
          color = ColorDefs[MT_COLOR_BOLD];
        else
          color ^= A_BOLD;
      }
289
    if ((special & A_UNDERLINE) || (a->attr & ANSI_UNDERLINE))
Thomas Roessler's avatar
Thomas Roessler committed
290 291 292 293 294 295
    {
      if (ColorDefs[MT_COLOR_UNDERLINE] && !search)
	color = ColorDefs[MT_COLOR_UNDERLINE];
      else
	color ^= A_UNDERLINE;
    }
296
    else if (a->attr & ANSI_REVERSE)
Thomas Roessler's avatar
Thomas Roessler committed
297 298 299
    {
      color ^= A_REVERSE;
    }
300
    else if (a->attr & ANSI_BLINK)
Thomas Roessler's avatar
Thomas Roessler committed
301 302 303
    {
      color ^= A_BLINK;
    }
304
    else if (a->attr == ANSI_OFF)
Thomas Roessler's avatar
Thomas Roessler committed
305 306 307 308 309 310 311
    {
      a->attr = 0;
    }
  }

  if (color != last_color)
  {
312
    ATTRSET (color);
Thomas Roessler's avatar
Thomas Roessler committed
313 314 315 316 317 318 319 320 321 322 323 324 325 326
    last_color = color;
  }
}

static void
append_line (struct line_t *lineInfo, int n, int cnt)
{
  int m;

  lineInfo[n+1].type = lineInfo[n].type;
  (lineInfo[n+1].syntax)[0].color = (lineInfo[n].syntax)[0].color;
  lineInfo[n+1].continuation = 1;

  /* find the real start of the line */
Thomas Roessler's avatar
Thomas Roessler committed
327
  for (m = n; m >= 0; m--)
Thomas Roessler's avatar
Thomas Roessler committed
328
    if (lineInfo[m].continuation == 0) break;
Thomas Roessler's avatar
Thomas Roessler committed
329

Thomas Roessler's avatar
Thomas Roessler committed
330
  (lineInfo[n+1].syntax)[0].first = m;
331
  (lineInfo[n+1].syntax)[0].last = (lineInfo[n].continuation) ?
Thomas Roessler's avatar
Thomas Roessler committed
332 333 334
    cnt + (lineInfo[n].syntax)[0].last : cnt;
}

335 336 337 338 339 340 341 342 343
static void
new_class_color (struct q_class_t *class, int *q_level)
{
  class->index = (*q_level)++;
  class->color = ColorQuote[class->index % ColorQuoteUsed];
}

static void
shift_class_colors (struct q_class_t *QuoteList, struct q_class_t *new_class,
344
                    int index, int *q_level)
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
{
  struct q_class_t * q_list;

  q_list = QuoteList;
  new_class->index = -1;

  while (q_list)
  {
    if (q_list->index >= index)
    {
      q_list->index++;
      q_list->color = ColorQuote[q_list->index % ColorQuoteUsed];
    }
    if (q_list->down)
      q_list = q_list->down;
    else if (q_list->next)
      q_list = q_list->next;
    else
    {
      while (!q_list->next)
      {
	q_list = q_list->up;
	if (q_list == NULL)
	  break;
      }
      if (q_list)
	q_list = q_list->next;
    }
  }

  new_class->index = index;
  new_class->color = ColorQuote[index % ColorQuoteUsed];
  (*q_level)++;
}

Thomas Roessler's avatar
Thomas Roessler committed
380 381 382 383 384 385 386 387 388 389
static void
cleanup_quote (struct q_class_t **QuoteList)
{
  struct q_class_t *ptr;

  while (*QuoteList)
  {
    if ((*QuoteList)->down)
      cleanup_quote (&((*QuoteList)->down));
    ptr = (*QuoteList)->next;
390
    if ((*QuoteList)->prefix)
391
      FREE (&(*QuoteList)->prefix);
392
    FREE (QuoteList);		/* __FREE_CHECKED__ */
Thomas Roessler's avatar
Thomas Roessler committed
393 394 395 396 397 398 399 400 401 402 403
    *QuoteList = ptr;
  }

  return;
}

static struct q_class_t *
classify_quote (struct q_class_t **QuoteList, const char *qptr,
		int length, int *force_redraw, int *q_level)
{
  struct q_class_t *q_list = *QuoteList;
404
  struct q_class_t *class = NULL, *tmp = NULL, *ptr, *save;
Thomas Roessler's avatar
Thomas Roessler committed
405 406
  char *tail_qptr;
  int offset, tail_lng;
407 408 409 410 411 412 413 414 415 416 417 418 419 420
  int index = -1;

  if (ColorQuoteUsed <= 1)
  {
    /* not much point in classifying quotes... */

    if (*QuoteList == NULL)
    {
      class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
      class->color = ColorQuote[0];
      *QuoteList = class;
    }
    return (*QuoteList);
  }
Thomas Roessler's avatar
Thomas Roessler committed
421 422 423 424 425 426 427 428

  /* Did I mention how much I like emulating Lisp in C? */

  /* classify quoting prefix */
  while (q_list)
  {
    if (length <= q_list->length)
    {
429 430
      /* case 1: check the top level nodes */

431
      if (mutt_strncmp (qptr, q_list->prefix, length) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
432 433
      {
	if (length == q_list->length)
434
	  return q_list;	/* same prefix: return the current class */
Thomas Roessler's avatar
Thomas Roessler committed
435

436
	/* found shorter prefix */
Thomas Roessler's avatar
Thomas Roessler committed
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
	if (tmp == NULL)
	{
	  /* add a node above q_list */
	  tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
	  tmp->prefix = (char *) safe_calloc (1, length + 1);
	  strncpy (tmp->prefix, qptr, length);
	  tmp->length = length;

	  /* replace q_list by tmp in the top level list */
	  if (q_list->next)
	  {
	    tmp->next = q_list->next;
	    q_list->next->prev = tmp;
	  }
	  if (q_list->prev)
	  {
	    tmp->prev = q_list->prev;
	    q_list->prev->next = tmp;
	  }

	  /* make q_list a child of tmp */
	  tmp->down = q_list;
	  q_list->up = tmp;

461
	  /* q_list has no siblings for now */
Thomas Roessler's avatar
Thomas Roessler committed
462 463 464 465 466 467 468
	  q_list->next = NULL;
	  q_list->prev = NULL;

	  /* update the root if necessary */
	  if (q_list == *QuoteList)
	    *QuoteList = tmp;

469 470
	  index = q_list->index;

Thomas Roessler's avatar
Thomas Roessler committed
471 472 473
	  /* tmp should be the return class too */
	  class = tmp;

474 475 476 477
	  /* next class to test; if tmp is a shorter prefix for another
	   * node, that node can only be in the top level list, so don't
	   * go down after this point
	   */
Thomas Roessler's avatar
Thomas Roessler committed
478 479 480 481
	  q_list = tmp->next;
	}
	else
	{
482 483
	  /* found another branch for which tmp is a shorter prefix */

Thomas Roessler's avatar
Thomas Roessler committed
484
	  /* save the next sibling for later */
485
	  save = q_list->next;
Thomas Roessler's avatar
Thomas Roessler committed
486 487 488 489 490 491 492 493

	  /* unlink q_list from the top level list */
	  if (q_list->next)
	    q_list->next->prev = q_list->prev;
	  if (q_list->prev)
	    q_list->prev->next = q_list->next;

	  /* at this point, we have a tmp->down; link q_list to it */
494 495 496 497 498 499 500
	  ptr = tmp->down;
	  /* sibling order is important here, q_list should be linked last */
	  while (ptr->next)
	    ptr = ptr->next;
	  ptr->next = q_list;
	  q_list->next = NULL;
	  q_list->prev = ptr;
Thomas Roessler's avatar
Thomas Roessler committed
501 502
	  q_list->up = tmp;

503 504 505 506
	  index = q_list->index;

	  /* next class to test; as above, we shouldn't go down */
	  q_list = save;
Thomas Roessler's avatar
Thomas Roessler committed
507 508
	}

509
	/* we found a shorter prefix, so certain quotes have changed classes */
Thomas Roessler's avatar
Thomas Roessler committed
510 511 512 513 514 515 516 517 518 519 520 521
	*force_redraw = 1;
	continue;
      }
      else
      {
	/* shorter, but not a substring of the current class: try next */
	q_list = q_list->next;
	continue;
      }
    }
    else
    {
522
      /* case 2: try subclassing the current top level node */
523

524
      /* tmp != NULL means we already found a shorter prefix at case 1 */
525
      if (tmp == NULL && mutt_strncmp (qptr, q_list->prefix, q_list->length) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
526
      {
527 528
	/* ok, it's a subclass somewhere on this branch */

Thomas Roessler's avatar
Thomas Roessler committed
529 530 531 532 533 534 535 536 537 538 539
	ptr = q_list;
	offset = q_list->length;

	q_list = q_list->down;
	tail_lng = length - offset;
	tail_qptr = (char *) qptr + offset;

	while (q_list)
	{
	  if (length <= q_list->length)
	  {
540
	    if (mutt_strncmp (tail_qptr, (q_list->prefix) + offset, tail_lng) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
541 542 543 544 545 546 547 548 549
	    {
	      /* same prefix: return the current class */
	      if (length == q_list->length)
		return q_list;

	      /* found shorter common prefix */
	      if (tmp == NULL)
	      {
		/* add a node above q_list */
550 551
		tmp = (struct q_class_t *)
                  safe_calloc (1, sizeof (struct q_class_t));
Thomas Roessler's avatar
Thomas Roessler committed
552 553 554
		tmp->prefix = (char *) safe_calloc (1, length + 1);
		strncpy (tmp->prefix, qptr, length);
		tmp->length = length;
555

Thomas Roessler's avatar
Thomas Roessler committed
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
		/* replace q_list by tmp */
		if (q_list->next)
		{
		  tmp->next = q_list->next;
		  q_list->next->prev = tmp;
		}
		if (q_list->prev)
		{
		  tmp->prev = q_list->prev;
		  q_list->prev->next = tmp;
		}

		/* make q_list a child of tmp */
		tmp->down = q_list;
		tmp->up = q_list->up;
		q_list->up = tmp;
		if (tmp->up->down == q_list)
		  tmp->up->down = tmp;

		/* q_list has no siblings */
		q_list->next = NULL;
		q_list->prev = NULL;
578

579 580
		index = q_list->index;

Thomas Roessler's avatar
Thomas Roessler committed
581 582 583 584 585 586 587 588
		/* tmp should be the return class too */
		class = tmp;

		/* next class to test */
		q_list = tmp->next;
	      }
	      else
	      {
589 590
		/* found another branch for which tmp is a shorter prefix */

Thomas Roessler's avatar
Thomas Roessler committed
591
		/* save the next sibling for later */
592
		save = q_list->next;
Thomas Roessler's avatar
Thomas Roessler committed
593 594 595 596 597 598 599 600

		/* unlink q_list from the top level list */
		if (q_list->next)
		  q_list->next->prev = q_list->prev;
		if (q_list->prev)
		  q_list->prev->next = q_list->next;

		/* at this point, we have a tmp->down; link q_list to it */
601 602 603 604 605 606
		ptr = tmp->down;
		while (ptr->next)
		  ptr = ptr->next;
		ptr->next = q_list;
		q_list->next = NULL;
		q_list->prev = ptr;
Thomas Roessler's avatar
Thomas Roessler committed
607 608
		q_list->up = tmp;

609 610
		index = q_list->index;

Thomas Roessler's avatar
Thomas Roessler committed
611
		/* next class to test */
612
		q_list = save;
Thomas Roessler's avatar
Thomas Roessler committed
613 614
	      }

615
	      /* we found a shorter prefix, so we need a redraw */
Thomas Roessler's avatar
Thomas Roessler committed
616 617 618 619 620 621 622 623 624 625 626 627
	      *force_redraw = 1;
	      continue;
	    }
	    else
	    {
	      q_list = q_list->next;
	      continue;
	    }
	  }
	  else
	  {
	    /* longer than the current prefix: try subclassing it */
628
	    if (tmp == NULL && mutt_strncmp (tail_qptr, (q_list->prefix) + offset,
629
                                             q_list->length - offset) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
	    {
	      /* still a subclass: go down one level */
	      ptr = q_list;
	      offset = q_list->length;

	      q_list = q_list->down;
	      tail_lng = length - offset;
	      tail_qptr = (char *) qptr + offset;

	      continue;
	    }
	    else
	    {
	      /* nope, try the next prefix */
	      q_list = q_list->next;
	      continue;
	    }
	  }
	}

650
	/* still not found so far: add it as a sibling to the current node */
Thomas Roessler's avatar
Thomas Roessler committed
651 652 653 654 655 656 657 658 659 660 661 662 663 664
	if (class == NULL)
	{
	  tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
	  tmp->prefix = (char *) safe_calloc (1, length + 1);
	  strncpy (tmp->prefix, qptr, length);
	  tmp->length = length;

	  if (ptr->down)
	  {
	    tmp->next = ptr->down;
	    ptr->down->prev = tmp;
	  }
	  ptr->down = tmp;
	  tmp->up = ptr;
665 666 667

	  new_class_color (tmp, q_level);

Thomas Roessler's avatar
Thomas Roessler committed
668 669 670
	  return tmp;
	}
	else
671 672 673 674
	{
	  if (index != -1)
	    shift_class_colors (*QuoteList, tmp, index, q_level);

Thomas Roessler's avatar
Thomas Roessler committed
675
	  return class;
676
	}
Thomas Roessler's avatar
Thomas Roessler committed
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
      }
      else
      {
	/* nope, try the next prefix */
	q_list = q_list->next;
	continue;
      }
    }
  }

  if (class == NULL)
  {
    /* not found so far: add it as a top level class */
    class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t));
    class->prefix = (char *) safe_calloc (1, length + 1);
    strncpy (class->prefix, qptr, length);
    class->length = length;
694
    new_class_color (class, q_level);
Thomas Roessler's avatar
Thomas Roessler committed
695 696 697 698 699 700 701 702 703

    if (*QuoteList)
    {
      class->next = *QuoteList;
      (*QuoteList)->prev = class;
    }
    *QuoteList = class;
  }

704 705 706
  if (index != -1)
    shift_class_colors (*QuoteList, tmp, index, q_level);

Thomas Roessler's avatar
Thomas Roessler committed
707 708 709
  return class;
}

710 711 712
static int brailleLine = -1;
static int brailleCol = -1;

713 714
static int check_attachment_marker (const char *);
static int check_protected_header_marker (const char *);
715

716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
/* Checks if buf matches the QuoteRegexp and doesn't match Smileys.
 * pmatch, if non-null, is populated with the regexec match against
 * QuoteRegexp.  This is used by the pager for calling classify_quote.
 */
int
mutt_is_quote_line (char *buf, regmatch_t *pmatch)
{
  int is_quote = 0;
  regmatch_t pmatch_internal[1], smatch[1];
  char c;

  if (!pmatch)
    pmatch = pmatch_internal;

  if (QuoteRegexp.rx &&
      regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0)
  {
    if (Smileys.rx &&
        regexec ((regex_t *) Smileys.rx, buf, 1, smatch, 0) == 0)
    {
      if (smatch[0].rm_so > 0)
      {
	c = buf[smatch[0].rm_so];
	buf[smatch[0].rm_so] = 0;

	if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0)
          is_quote = 1;

	buf[smatch[0].rm_so] = c;
      }
    }
    else
      is_quote = 1;
  }

  return is_quote;
}

Thomas Roessler's avatar
Thomas Roessler committed
754
static void
Thomas Roessler's avatar
Thomas Roessler committed
755
resolve_types (char *buf, char *raw, struct line_t *lineInfo, int n, int last,
756 757
               struct q_class_t **QuoteList, int *q_level, int *force_redraw,
               int q_classify)
Thomas Roessler's avatar
Thomas Roessler committed
758
{
759
  COLOR_LINE *color_line, *color_list;
760
  regmatch_t pmatch[1];
Thomas Roessler's avatar
Thomas Roessler committed
761 762
  int found, offset, null_rx, i;

763 764
  if (n == 0 || ISHEADER (lineInfo[n-1].type) ||
      (check_protected_header_marker (raw) == 0))
Thomas Roessler's avatar
Thomas Roessler committed
765
  {
766 767
    if (buf[0] == '\n') /* end of header */
    {
Thomas Roessler's avatar
Thomas Roessler committed
768
      lineInfo[n].type = MT_COLOR_NORMAL;
769
      getyx(stdscr, brailleLine, brailleCol);
Thomas Roessler's avatar
Thomas Roessler committed
770 771 772
    }
    else
    {
773 774 775 776 777
      /* if this is a continuation of the previous line, use the previous
       * line's color as default. */
      if (n > 0 && (buf[0] == ' ' || buf[0] == '\t'))
      {
	lineInfo[n].type = lineInfo[n-1].type; /* wrapped line */
778 779 780 781 782
        if (!option (OPTHEADERCOLORPARTIAL))
        {
          (lineInfo[n].syntax)[0].color = (lineInfo[n-1].syntax)[0].color;
          lineInfo[n].is_cont_hdr = 1;
        }
783 784 785 786 787 788
      }
      else
      {
	lineInfo[n].type = MT_COLOR_HDEFAULT;
      }

789 790 791 792 793
      /* When this option is unset, we color the entire header the
       * same color.  Otherwise, we handle the header patterns just
       * like body patterns (further below).
       */
      if (!option (OPTHEADERCOLORPARTIAL))
Thomas Roessler's avatar
Thomas Roessler committed
794
      {
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
        for (color_line = ColorHdrList; color_line; color_line = color_line->next)
        {
          if (REGEXEC (color_line->rx, buf) == 0)
          {
            lineInfo[n].type = MT_COLOR_HEADER;
            lineInfo[n].syntax[0].color = color_line->pair;
            if (lineInfo[n].is_cont_hdr)
            {
              /* adjust the previous continuation lines to reflect the color of this continuation line */
              int j;
              for (j = n - 1; j >= 0 && lineInfo[j].is_cont_hdr; --j)
              {
                lineInfo[j].type = lineInfo[n].type;
                lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color;
              }
              /* now adjust the first line of this header field */
              if (j >= 0)
              {
                lineInfo[j].type = lineInfo[n].type;
                lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color;
              }
              *force_redraw = 1; /* the previous lines have already been drawn on the screen */
            }
            break;
          }
        }
Thomas Roessler's avatar
Thomas Roessler committed
821 822 823
      }
    }
  }
824
  else if (mutt_strncmp ("\033[0m", raw, 4) == 0)	/* a little hack... */
Thomas Roessler's avatar
Thomas Roessler committed
825
    lineInfo[n].type = MT_COLOR_NORMAL;
826 827
  else if (check_attachment_marker ((char *) raw) == 0)
    lineInfo[n].type = MT_COLOR_ATTACHMENT;
828
  else if (mutt_strcmp ("-- \n", buf) == 0 || mutt_strcmp ("-- \r\n", buf) == 0)
Thomas Roessler's avatar
Thomas Roessler committed
829 830 831 832 833 834 835 836
  {
    i = n + 1;

    lineInfo[n].type = MT_COLOR_SIGNATURE;
    while (i < last && check_sig (buf, lineInfo, i - 1) == 0 &&
	   (lineInfo[i].type == MT_COLOR_NORMAL ||
	    lineInfo[i].type == MT_COLOR_QUOTED ||
	    lineInfo[i].type == MT_COLOR_HEADER))
837 838 839
    {
      /* oops... */
      if (lineInfo[i].chunks)
Thomas Roessler's avatar
Thomas Roessler committed
840
      {
841 842 843
        lineInfo[i].chunks = 0;
        safe_realloc (&(lineInfo[n].syntax),
                      sizeof (struct syntax_t));
Thomas Roessler's avatar
Thomas Roessler committed
844
      }
845 846
      lineInfo[i++].type = MT_COLOR_SIGNATURE;
    }
Thomas Roessler's avatar
Thomas Roessler committed
847 848 849
  }
  else if (check_sig (buf, lineInfo, n - 1) == 0)
    lineInfo[n].type = MT_COLOR_SIGNATURE;
850
  else if (mutt_is_quote_line (buf, pmatch))
Thomas Roessler's avatar
Thomas Roessler committed
851
  {
852 853 854 855 856
    if (q_classify && lineInfo[n].quote == NULL)
      lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so,
                                          pmatch[0].rm_eo - pmatch[0].rm_so,
                                          force_redraw, q_level);
    lineInfo[n].type = MT_COLOR_QUOTED;
Thomas Roessler's avatar
Thomas Roessler committed
857 858 859 860 861
  }
  else
    lineInfo[n].type = MT_COLOR_NORMAL;

  /* body patterns */
862 863 864
  if (lineInfo[n].type == MT_COLOR_NORMAL ||
      lineInfo[n].type == MT_COLOR_QUOTED ||
      (lineInfo[n].type == MT_COLOR_HDEFAULT && option (OPTHEADERCOLORPARTIAL)))
Thomas Roessler's avatar
Thomas Roessler committed
865
  {
866 867 868 869 870 871
    size_t nl;

    /* don't consider line endings part of the buffer
     * for regex matching */
    if ((nl = mutt_strlen (buf)) > 0 && buf[nl-1] == '\n')
      buf[nl-1] = 0;
Thomas Roessler's avatar
Thomas Roessler committed
872

873
    i = 0;
Thomas Roessler's avatar
Thomas Roessler committed
874 875
    offset = 0;
    lineInfo[n].chunks = 0;
876 877 878 879 880 881 882 883 884 885
    if (lineInfo[n].type == MT_COLOR_HDEFAULT)
      color_list = ColorHdrList;
    else
      color_list = ColorBodyList;
    color_line = color_list;
    while (color_line)
    {
      color_line->stop_matching = 0;
      color_line = color_line->next;
    }
Thomas Roessler's avatar
Thomas Roessler committed
886 887 888 889 890 891 892
    do
    {
      if (!buf[offset])
	break;

      found = 0;
      null_rx = 0;
893
      color_line = color_list;
Thomas Roessler's avatar
Thomas Roessler committed
894 895
      while (color_line)
      {
896 897
	if (!color_line->stop_matching &&
            regexec (&color_line->rx, buf + offset, 1, pmatch,
Thomas Roessler's avatar
Thomas Roessler committed
898 899 900 901 902 903
		     (offset ? REG_NOTBOL : 0)) == 0)
	{
	  if (pmatch[0].rm_eo != pmatch[0].rm_so)
	  {
	    if (!found)
	    {
904 905 906 907 908 909 910
              /* Abort if we fill up chunks.
               * Yes, this really happened. See #3888 */
              if (lineInfo[n].chunks == SHRT_MAX)
              {
                null_rx = 0;
                break;
              }
Thomas Roessler's avatar
Thomas Roessler committed
911
	      if (++(lineInfo[n].chunks) > 1)
912
		safe_realloc (&(lineInfo[n].syntax),
Thomas Roessler's avatar
Thomas Roessler committed
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
			      (lineInfo[n].chunks) * sizeof (struct syntax_t));
	    }
	    i = lineInfo[n].chunks - 1;
	    pmatch[0].rm_so += offset;
	    pmatch[0].rm_eo += offset;
	    if (!found ||
		pmatch[0].rm_so < (lineInfo[n].syntax)[i].first ||
		(pmatch[0].rm_so == (lineInfo[n].syntax)[i].first &&
		 pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last))
	    {
	      (lineInfo[n].syntax)[i].color = color_line->pair;
	      (lineInfo[n].syntax)[i].first = pmatch[0].rm_so;
	      (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo;
	    }
	    found = 1;
	    null_rx = 0;
	  }
	  else
	    null_rx = 1; /* empty regexp; don't add it, but keep looking */
	}
933 934 935 936 937
        /* Once a regexp fails to match, don't try matching it again.
         * On very long lines this can cause a performance issue if there
         * are other regexps that have many matches. */
        else
          color_line->stop_matching = 1;
Thomas Roessler's avatar
Thomas Roessler committed
938 939 940 941 942 943 944 945
	color_line = color_line->next;
      }

      if (null_rx)
	offset++; /* avoid degenerate cases */
      else
	offset = (lineInfo[n].syntax)[i].last;
    } while (found || null_rx);
946 947
    if (nl > 0)
      buf[nl] = '\n';
Thomas Roessler's avatar
Thomas Roessler committed
948 949 950
  }
}

951 952
static int is_ansi (unsigned char *buf)
{
953
  while (*buf && (isdigit(*buf) || *buf == ';'))
954 955 956 957
    buf++;
  return (*buf == 'm');
}

958
static int check_marker (const char *q, const char *p)
959 960 961 962 963
{
  for (;*p == *q && *q && *p && *q != '\a' && *p != '\a'; p++, q++)
    ;
  return (int) (*p - *q);
}
Thomas Roessler's avatar
Thomas Roessler committed
964

965 966 967 968 969 970 971 972 973 974
static int check_attachment_marker (const char *p)
{
  return check_marker (AttachmentMarker, p);
}

static int check_protected_header_marker (const char *p)
{
  return check_marker (ProtectedHeaderMarker, p);
}

Thomas Roessler's avatar
Thomas Roessler committed
975 976 977 978 979 980 981 982
static int grok_ansi(unsigned char *buf, int pos, ansi_attr *a)
{
  int x = pos;

  while (isdigit(buf[x]) || buf[x] == ';')
    x++;

  /* Character Attributes */
983
  if (option (OPTALLOWANSI) && a != NULL && buf[x] == 'm')
Thomas Roessler's avatar
Thomas Roessler committed
984
  {
985 986 987 988 989 990 991 992 993
    if (pos == x)
    {
#ifdef HAVE_COLOR
      if (a->pair != -1)
	mutt_free_color (a->fg, a->bg);
#endif
      a->attr = ANSI_OFF;
      a->pair = -1;
    }
Thomas Roessler's avatar
Thomas Roessler committed
994 995 996 997 998 999
    while (pos < x)
    {
      if (buf[pos] == '1' && (pos+1 == x || buf[pos+1] == ';'))
      {
	a->attr |= ANSI_BOLD;
	pos += 2;
1000
      }
Thomas Roessler's avatar
Thomas Roessler committed
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
      else if (buf[pos] == '4' && (pos+1 == x || buf[pos+1] == ';'))
      {
	a->attr |= ANSI_UNDERLINE;
	pos += 2;
      }
      else if (buf[pos] == '5' && (pos+1 == x || buf[pos+1] == ';'))
      {
	a->attr |= ANSI_BLINK;
	pos += 2;
      }
      else if (buf[pos] == '7' && (pos+1 == x || buf[pos+1] == ';'))
      {
	a->attr |= ANSI_REVERSE;
	pos += 2;
      }
      else if (buf[pos] == '0' && (pos+1 == x || buf[pos+1] == ';'))
      {
#ifdef HAVE_COLOR
	if (a->pair != -1)
	  mutt_free_color(a->fg,a->bg);
#endif
	a->attr = ANSI_OFF;
	a->pair = -1;
	pos += 2;
      }
      else if (buf[pos] == '3' && isdigit(buf[pos+1]))
      {
1028 1029 1030 1031 1032
#ifdef HAVE_COLOR
	if (a->pair != -1)
	  mutt_free_color(a->fg,a->bg);
#endif
	a->pair = -1;
Thomas Roessler's avatar
Thomas Roessler committed
1033 1034 1035 1036 1037 1038
	a->attr |= ANSI_COLOR;
	a->fg = buf[pos+1] - '0';
	pos += 3;
      }
      else if (buf[pos] == '4' && isdigit(buf[pos+1]))
      {
1039 1040 1041 1042 1043
#ifdef HAVE_COLOR
	if (a->pair != -1)
	  mutt_free_color(a->fg,a->bg);
#endif
	a->pair = -1;
Thomas Roessler's avatar
Thomas Roessler committed
1044 1045 1046 1047
	a->attr |= ANSI_COLOR;
	a->bg = buf[pos+1] - '0';
	pos += 3;
      }
1048
      else
Thomas Roessler's avatar
Thomas Roessler committed
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058
      {
	while (pos < x && buf[pos] != ';') pos++;
	pos++;
      }
    }
  }
  pos = x;
  return pos;
}

Thomas Roessler's avatar
Thomas Roessler committed
1059
static int
1060 1061
fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf,
	     unsigned char **fmt, size_t *blen, int *buf_ready)
Thomas Roessler's avatar
Thomas Roessler committed
1062
{
1063
  unsigned char *p, *q;
Thomas Roessler's avatar
Thomas Roessler committed
1064
  static int b_read;
1065
  int l;
1066

Thomas Roessler's avatar
Thomas Roessler committed
1067 1068 1069
  if (*buf_ready == 0)
  {
    if (offset != *last_pos)
1070
      fseeko (f, offset, 0);
1071
    if ((*buf = (unsigned char *) mutt_read_line ((char *) *buf, blen, f, &l, MUTT_EOL)) == NULL)
Thomas Roessler's avatar
Thomas Roessler committed
1072 1073 1074 1075
    {
      fmt[0] = 0;
      return (-1);
    }
1076
    *last_pos = ftello (f);
Thomas Roessler's avatar
Thomas Roessler committed
1077 1078 1079
    b_read = (int) (*last_pos - offset);
    *buf_ready = 1;

1080 1081
    safe_realloc (fmt, *blen);

Thomas Roessler's avatar
Thomas Roessler committed
1082
    /* copy "buf" to "fmt", but without bold and underline controls */
1083 1084
    p = *buf;
    q = *fmt;
Thomas Roessler's avatar
Thomas Roessler committed
1085 1086
    while (*p)
    {
1087
      if (*p == '\010' && (p > *buf))
Thomas Roessler's avatar
Thomas Roessler committed
1088 1089 1090
      {
	if (*(p+1) == '_')	/* underline */
	  p += 2;
1091
	else if (*(p+1) && q > *fmt)	/* bold or overstrike */
Thomas Roessler's avatar
Thomas Roessler committed
1092
	{
1093
	  *(q-1) = *(p+1);
Thomas Roessler's avatar
Thomas Roessler committed
1094 1095 1096
	  p += 2;
	}
	else			/* ^H */
1097
	  *q++ = *p++;
Thomas Roessler's avatar
Thomas Roessler committed
1098
      }
1099 1100 1101 1102 1103
      else if (*p == '\033' && *(p+1) == '[' && is_ansi (p + 2))
      {
	while (*p++ != 'm')	/* skip ANSI sequence */
	  ;
      }
1104 1105 1106
      else if (*p == '\033' && *(p+1) == ']' &&
               ((check_attachment_marker ((char *) p) == 0) ||
                (check_protected_header_marker ((char *) p) == 0)))
1107 1108 1109
      {
	dprint (2, (debugfile, "fill_buffer: Seen attachment marker.\n"));
	while (*p++ != '\a')	/* skip pseudo-ANSI sequence */
Thomas Roessler's avatar
Thomas Roessler committed
1110
	  ;
1111
      }
Thomas Roessler's avatar
Thomas Roessler committed
1112
      else
1113
	*q++ = *p++;
Thomas Roessler's avatar
Thomas Roessler committed
1114
    }
1115
    *q = 0;
Thomas Roessler's avatar
Thomas Roessler committed
1116 1117 1118 1119 1120
  }
  return b_read;
}


1121 1122
static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
			int flags, ansi_attr *pa, int cnt,
1123 1124
			int *pspace, int *pvch, int *pcol, int *pspecial,
                        mutt_window_t *pager_window)
1125 1126 1127
{
  int space = -1; /* index of the last space or TAB */
  int col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0;
1128 1129
  size_t k;
  int ch, vch, last_special = -1, special = 0, t;
1130
  wchar_t wc;
1131
  mbstate_t mbstate;
1132
  int wrap_cols = mutt_window_wrap_cols (pager_window, (flags & MUTT_PAGER_NOWRAP) ? 0 : Wrap);
1133 1134

  if (check_attachment_marker ((char *)buf) == 0)
1135
    wrap_cols = pager_window->cols;
1136

1137 1138
  /* FIXME: this should come from lineInfo */
  memset(&mbstate, 0, sizeof(mbstate));
1139 1140 1141 1142 1143 1144 1145

  for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k)
  {
    /* Handle ANSI sequences */
    while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == '[' &&
	   is_ansi (buf+ch+2))
      ch = grok_ansi (buf, ch+2, pa) + 1;
1146 1147

    while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == ']' &&
1148 1149
	   ((check_attachment_marker ((char *) buf+ch) == 0) ||
            (check_protected_header_marker ((char *) buf+ch) == 0)))
1150 1151
    {
      while (buf[ch++] != '\a')
1152 1153
	if (ch >= cnt)
	  break;
1154 1155
    }

1156 1157 1158
    /* is anything left to do? */
    if (ch >= cnt)
      break;
1159

1160
    k = mbrtowc (&wc, (char *)buf+ch, cnt-ch, &mbstate);
1161
    if (k == (size_t)(-2) || k == (size_t)(-1))
1162
    {
1163
      if (k == (size_t)(-1))
1164
        memset(&mbstate, 0, sizeof(mbstate));
Thomas Roessler's avatar
Thomas Roessler committed
1165 1166
      dprint (1, (debugfile, "%s:%d: mbrtowc returned %d; errno = %d.\n",
		  __FILE__, __LINE__, k, errno));
1167
      if (col + 4 > wrap_cols)
1168 1169 1170 1171 1172 1173 1174 1175
	break;
      col += 4;
      if (pa)
	printw ("\\%03o", buf[ch]);
      k = 1;
      continue;
    }
    if (k == 0)
1176
      k = 1;
1177

1178
    if (Charset_is_utf8)
1179
    {
1180 1181
      if (wc == 0x200B || wc == 0xFEFF)
      {
1182
	dprint (3, (debugfile, "skip zero-width character U+%04X\n", (unsigned short)wc));
1183
	continue;
1184
      }
1185
      if (is_display_corrupting_utf8 (wc))
1186
      {
1187
	dprint (3, (debugfile, "filtered U+%04X\n", (unsigned short)wc));
1188 1189
	continue;
      }
1190 1191
    }

1192 1193
    /* Handle backspace */
    special = 0;
1194
    if (IsWPrint (wc))
1195 1196 1197
    {
      wchar_t wc1;
      mbstate_t mbstate1;
1198 1199 1200 1201 1202 1203
      size_t k1, k2;

      mbstate1 = mbstate;
      k1 = mbrtowc (&wc1, (char *)buf+ch+k, cnt-ch-k, &mbstate1);
      while ((k1 != (size_t)(-2)) && (k1 != (size_t)(-1)) &&
             (k1 > 0) && (wc1 == '\b'))
1204
      {
1205 1206 1207 1208 1209
        k2 = mbrtowc (&wc1, (char *)buf+ch+k+k1, cnt-ch-k-k1, &mbstate1);
        if ((k2 == (size_t)(-2)) || (k2 == (size_t)(-1)) ||
            (k2 == 0) || (!IsWPrint (wc1)))
          break;

1210 1211
	if (wc == wc1)
	{
1212
	  special |= (wc == '_' && special & A_UNDERLINE)
1213 1214 1215 1216
	    ? A_UNDERLINE : A_BOLD;
	}
	else if (wc == '_' || wc1 == '_')
	{
1217
	  special |= A_UNDERLINE;
1218 1219 1220 1221
	  wc = (wc1 == '_') ? wc : wc1;
	}
	else
	{
1222
	  /* special = 0; / * overstrike: nothing to do! */
1223 1224
	  wc = wc1;
	}
1225 1226

	ch += k + k1;
1227 1228
	k = k2;
	mbstate = mbstate1;
1229
        k1 = mbrtowc (&wc1, (char *)buf+ch+k, cnt-ch-k, &mbstate1);
1230 1231 1232 1233
      }
    }

    if (pa &&
1234
	((flags & (MUTT_SHOWCOLOR | MUTT_SEARCH | MUTT_PAGER_MARKER)) ||
1235 1236
	 special || last_special || pa->attr))
    {
1237
      resolve_color (*lineInfo, n, vch, flags, special, pa);
1238 1239
      last_special = special;
    }
1240

1241
    if (IsWPrint (wc) || (Charset_is_utf8 && wc == 0x00A0))
1242 1243 1244 1245
    {
      if (wc == ' ')
	space = ch;
      t = wcwidth (wc);
1246
      if (col + t > wrap_cols)
1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
	break;
      col += t;
      if (pa)
	mutt_addwch (wc);
    }
    else if (wc == '\n')
      break;
    else if (wc == '\t')
    {
      space = ch;
      t = (col & ~7) + 8;
1258
      if (t > wrap_cols)
1259 1260 1261 1262 1263 1264 1265 1266 1267
	break;
      if (pa)
	for (; col < t; col++)
	  addch (' ');
      else
	col = t;
    }
    else if (wc < 0x20 || wc == 0x7f)
    {
1268
      if (col + 2 > wrap_cols)
1269 1270 1271 1272 1273 1274 1275
	break;
      col += 2;
      if (pa)
	printw ("^%c", ('@' + wc) & 0x7f);
    }
    else if (wc < 0x100)
    {
1276
      if (col + 4 > wrap_cols)
1277 1278 1279 1280 1281 1282 1283
	break;
      col += 4;
      if (pa)
	printw ("\\%03o", wc);
    }
    else
    {
1284
      if (col + 1 > wrap_cols)
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297
	break;
      ++col;
      if (pa)
	addch (replacement_char ());
    }
  }
  *pspace = space;
  *pcol = col;
  *pvch = vch;
  *pspecial = special;
  return ch;
}

Thomas Roessler's avatar
Thomas Roessler committed
1298 1299
/*
 * Args:
1300 1301
 *	flags	MUTT_SHOWFLAT, show characters (used for displaying help)
 *		MUTT_SHOWCOLOR, show characters in color
1302
 *			otherwise don't show characters
1303 1304 1305 1306 1307
 *		MUTT_HIDE, don't show quoted text
 *		MUTT_SEARCH, resolve search patterns
 *		MUTT_TYPES, compute line's type
 *		MUTT_PAGER_NSKIP, keeps leading whitespace
 *		MUTT_PAGER_MARKER, eventually show markers
Thomas Roessler's avatar
Thomas Roessler committed
1308 1309 1310 1311 1312 1313 1314 1315
 *
 * Return values:
 *	-1	EOF was reached
 *	0	normal exit, line was not displayed
 *	>0	normal exit, line was displayed
 */

static int
1316
display_line (FILE *f, LOFF_T *last_pos, struct line_t **lineInfo, int n,
Thomas Roessler's avatar
Thomas Roessler committed
1317
	      int *last, int *max, int flags, struct q_class_t **QuoteList,
1318 1319
	      int *q_level, int *force_redraw, regex_t *SearchRE,
              mutt_window_t *pager_window)
Thomas Roessler's avatar
Thomas Roessler committed
1320
{
1321 1322
  unsigned char *buf = NULL, *fmt = NULL;
  size_t buflen = 0;
1323 1324
  unsigned char *buf_ptr = buf;
  int ch, vch, col, cnt, b_read;
Thomas Roessler's avatar
Thomas Roessler committed
1325
  int buf_ready = 0, change_last = 0;
1326
  int special;
Thomas Roessler's avatar
Thomas Roessler committed
1327 1328 1329
  int offset;
  int def_color;
  int m;
1330
  int rc = -1;
Thomas Roessler's avatar
Thomas Roessler committed
1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
  ansi_attr a = {0,0,0,-1};
  regmatch_t pmatch[1];

  if (n == *last)
  {
    (*last)++;
    change_last = 1;
  }

  if (*last == *max)
  {
1342
    safe_realloc (lineInfo, sizeof (struct line_t) * (*max += LINES));
Thomas Roessler's avatar
Thomas Roessler committed
1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353
    for (ch = *last; ch < *max ; ch++)
    {
      memset (&((*lineInfo)[ch]), 0, sizeof (struct line_t));
      (*lineInfo)[ch].type = -1;
      (*lineInfo)[ch].search_cnt = -1;
      (*lineInfo)[ch].syntax = safe_malloc (sizeof (struct syntax_t));
      ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last = -1;
    }
  }

  /* only do color hiliting if we are viewing a message */
1354
  if (flags & (MUTT_SHOWCOLOR | MUTT_TYPES))
Thomas Roessler's avatar
Thomas Roessler committed
1355 1356 1357 1358
  {
    if ((*lineInfo)[n].type == -1)
    {
      /* determine the line class */
1359
      if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
1360 1361 1362
      {
	if (change_last)
	  (*last)--;
1363
	goto out;
Thomas Roessler's avatar
Thomas Roessler committed
1364 1365
      }

Thomas Roessler's avatar
Thomas Roessler committed
1366
      resolve_types ((char *) fmt, (char *) buf, *lineInfo, n, *last,
1367
                     QuoteList, q_level, force_redraw, flags & MUTT_SHOWCOLOR);
1368 1369 1370 1371

      /* avoid race condition for continuation lines when scrolling up */
      for (m = n + 1; m < *last && (*lineInfo)[m].offset && (*lineInfo)[m].continuation; m++)
	(*lineInfo)[m].type = (*lineInfo)[n].type;
Thomas Roessler's avatar
Thomas Roessler committed
1372 1373 1374
    }

    /* this also prevents searching through the hidden lines */
1375 1376
    if ((flags & MUTT_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED)
      flags = 0; /* MUTT_NOSHOW */
Thomas Roessler's avatar
Thomas Roessler committed
1377 1378
  }

1379
  /* At this point, (*lineInfo[n]).quote may still be undefined. We
1380
   * don't want to compute it every time MUTT_TYPES is set, since this
Thomas Roessler's avatar
Thomas Roessler committed
1381 1382 1383 1384
   * would slow down the "bottom" function unacceptably. A compromise
   * solution is hence to call regexec() again, just to find out the
   * length of the quote prefix.
   */
1385
  if ((flags & MUTT_SHOWCOLOR) && !(*lineInfo)[n].continuation &&
Thomas Roessler's avatar
Thomas Roessler committed
1386 1387
      (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL)
  {
1388
    if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
1389 1390 1391
    {
      if (change_last)
	(*last)--;
1392
      goto out;
Thomas Roessler's avatar
Thomas Roessler committed
1393 1394
    }
    regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0);
1395 1396 1397 1398 1399
    (*lineInfo)[n].quote =
      classify_quote (QuoteList,
                      (char *) fmt + pmatch[0].rm_so,
                      pmatch[0].rm_eo - pmatch[0].rm_so,
                      force_redraw, q_level);
Thomas Roessler's avatar
Thomas Roessler committed
1400 1401
  }

1402
  if ((flags & MUTT_SEARCH) && !(*lineInfo)[n].continuation && (*lineInfo)[n].search_cnt == -1)
Thomas Roessler's avatar
Thomas Roessler committed
1403
  {
1404
    if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
1405 1406 1407
    {
      if (change_last)
	(*last)--;
1408
      goto out;
Thomas Roessler's avatar
Thomas Roessler committed
1409 1410 1411 1412 1413 1414 1415
    }

    offset = 0;
    (*lineInfo)[n].search_cnt = 0;
    while (regexec (SearchRE, (char *) fmt + offset, 1, pmatch, (offset ? REG_NOTBOL : 0)) == 0)
    {
      if (++((*lineInfo)[n].search_cnt) > 1)
1416
	safe_realloc (&((*lineInfo)[n].search),
Thomas Roessler's avatar
Thomas Roessler committed
1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
		      ((*lineInfo)[n].search_cnt) * sizeof (struct syntax_t));
      else
	(*lineInfo)[n].search = safe_malloc (sizeof (struct syntax_t));
      pmatch[0].rm_so += offset;
      pmatch[0].rm_eo += offset;
      ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first = pmatch[0].rm_so;
      ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last = pmatch[0].rm_eo;

      if (pmatch[0].rm_eo == pmatch[0].rm_so)
	offset++; /* avoid degenerate cases */
      else
	offset = pmatch[0].rm_eo;
      if (!fmt[offset])
	break;
    }
  }

1434
  if (!(flags & MUTT_SHOW) && (*lineInfo)[n+1].offset > 0)
Thomas Roessler's avatar
Thomas Roessler committed
1435 1436
  {
    /* we've already scanned this line, so just exit */
1437 1438
    rc = 0;
    goto out;
Thomas Roessler's avatar
Thomas Roessler committed
1439
  }
1440
  if ((flags & MUTT_SHOWCOLOR) && *force_redraw && (*lineInfo)[n+1].offset > 0)
Thomas Roessler's avatar
Thomas Roessler committed
1441 1442
  {
    /* no need to try to display this line... */
1443 1444
    rc = 1;
    goto out; /* fake display */
Thomas Roessler's avatar
Thomas Roessler committed
1445 1446
  }

1447
  if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt,
1448
			     &buflen, &buf_ready)) < 0)
Thomas Roessler's avatar
Thomas Roessler committed
1449 1450 1451
  {
    if (change_last)
      (*last)--;
1452
    goto out;
Thomas Roessler's avatar
Thomas Roessler committed
1453 1454 1455
  }

  /* now chose a good place to break the line */
1456 1457
  cnt = format_line (lineInfo, n, buf, flags, 0, b_read, &ch, &vch, &col, &special,
                     pager_window);
1458
  buf_ptr = buf + cnt;
Thomas Roessler's avatar
Thomas Roessler committed
1459 1460 1461 1462

  /* move the break point only if smart_wrap is set */
  if (option (OPTWRAP))
  {
1463 1464 1465
    if (cnt < b_read &&
        ch != -1 &&
        buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n' && buf[cnt] != '\r')
Thomas Roessler's avatar
Thomas Roessler committed
1466
    {
1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477
      buf_ptr = buf + ch;
      /* skip trailing blanks */
      while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r'))
        ch--;
      /* A very long word with leading spaces causes infinite
       * wrapping when MUTT_PAGER_NSKIP is set.  A folded header
       * with a single long word shouldn't be smartwrapped
       * either.  So just disable smart_wrap if it would wrap at the
       * beginning of the line. */
      if (!ch)
        buf_ptr = buf + cnt;
Thomas Roessler's avatar
Thomas Roessler committed
1478
      else
1479
        cnt = ch + 1;
Thomas Roessler's avatar
Thomas Roessler committed
1480
    }
1481
    if (!(flags & MUTT_PAGER_NSKIP))
1482
      /* skip leading blanks on the next line too */
1483
      while (*buf_ptr == ' ' || *buf_ptr == '\t')
1484
	buf_ptr++;
Thomas Roessler's avatar
Thomas Roessler committed
1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496
  }

  if (*buf_ptr == '\r')
    buf_ptr++;
  if (*buf_ptr == '\n')
    buf_ptr++;

  if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n+1].continuation)
    append_line (*lineInfo, n, (int) (buf_ptr - buf));
  (*lineInfo)[n+1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf);

  /* if we don't need to display the line we are done */
1497
  if (!(flags & MUTT_SHOW))
1498 1499 1500 1501
  {
    rc = 0;
    goto out;
  }
Thomas Roessler's avatar
Thomas Roessler committed
1502

1503
  /* display the line */
1504 1505
  format_line (lineInfo, n, buf, flags, &a, cnt, &ch, &vch, &col, &special,
               pager_window);
Thomas Roessler's avatar
Thomas Roessler committed
1506 1507 1508 1509 1510

  /* avoid a bug in ncurses... */
#ifndef USE_SLANG_CURSES
  if (col == 0)
  {
1511
    NORMAL_COLOR;
Thomas Roessler's avatar
Thomas Roessler committed
1512 1513 1514 1515 1516
    addch (' ');
  }
#endif

  /* end the last color pattern (needed by S-Lang) */
1517
  if (special || (col != pager_window->cols && (flags & (MUTT_SHOWCOLOR | MUTT_SEARCH))))
Thomas Roessler's avatar
Thomas Roessler committed
1518
    resolve_color (*lineInfo, n, vch, flags, 0, &a);
1519

1520 1521 1522 1523 1524
  /*
   * Fill the blank space at the end of the line with the prevailing color.
   * ncurses does an implicit clrtoeol() when you do addch('\n') so we have
   * to make sure to reset the color *after* that
   */
1525
  if (flags & MUTT_SHOWCOLOR)
1526 1527 1528 1529 1530 1531 1532
  {
    m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n;
    if ((*lineInfo)[m].type