mutt stable branch with some hacks
at jcs 2965 lines 77 kB view raw
1/* 2 * Copyright (C) 1996-2002,2007,2010,2012-2013 Michael R. Elkins <me@mutt.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * 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 */ 18 19#if HAVE_CONFIG_H 20# include "config.h" 21#endif 22 23#include "mutt.h" 24#include "mutt_curses.h" 25#include "mutt_regex.h" 26#include "keymap.h" 27#include "mutt_menu.h" 28#include "mapping.h" 29#include "pager.h" 30#include "attach.h" 31#include "mbyte.h" 32#include "sort.h" 33#include "buffy.h" 34#ifdef USE_SIDEBAR 35#include "sidebar.h" 36#endif 37 38#include "mutt_crypt.h" 39 40#include <sys/stat.h> 41#include <ctype.h> 42#include <unistd.h> 43#include <stdlib.h> 44#include <string.h> 45#include <errno.h> 46 47#define ISHEADER(x) ((x) == MT_COLOR_HEADER || (x) == MT_COLOR_HDEFAULT) 48 49#define IsAttach(x) (x && (x)->bdy) 50#define IsRecvAttach(x) (x && (x)->bdy && (x)->fp) 51#define IsSendAttach(x) (x && (x)->bdy && !(x)->fp) 52#define IsMsgAttach(x) (x && (x)->fp && (x)->bdy && (x)->bdy->hdr) 53#define IsHeader(x) (x && (x)->hdr && !(x)->bdy) 54 55static const char *Not_available_in_this_menu = N_("Not available in this menu."); 56static const char *Mailbox_is_read_only = N_("Mailbox is read-only."); 57static const char *Function_not_permitted_in_attach_message_mode = N_("Function not permitted in attach-message mode."); 58 59/* hack to return to position when returning from index to same message */ 60static int TopLine = 0; 61static HEADER *OldHdr = NULL; 62 63#define CHECK_MODE(x) \ 64 if (!(x)) \ 65 { \ 66 mutt_flushinp (); \ 67 mutt_error _(Not_available_in_this_menu); \ 68 break; \ 69 } 70 71#define CHECK_READONLY \ 72 if (Context->readonly) \ 73 { \ 74 mutt_flushinp (); \ 75 mutt_error _(Mailbox_is_read_only); \ 76 break; \ 77 } 78 79#define CHECK_ATTACH \ 80 if (option(OPTATTACHMSG)) \ 81 { \ 82 mutt_flushinp (); \ 83 mutt_error _(Function_not_permitted_in_attach_message_mode); \ 84 break; \ 85 } 86 87#define CHECK_ACL(aclbit,action) \ 88 if (!mutt_bit_isset(Context->rights,aclbit)) \ 89 { \ 90 mutt_flushinp(); \ 91 /* L10N: %s is one of the CHECK_ACL entries below. */ \ 92 mutt_error (_("%s: Operation not permitted by ACL"), action); \ 93 break; \ 94 } 95 96struct q_class_t 97{ 98 int length; 99 int index; 100 int color; 101 char *prefix; 102 struct q_class_t *next, *prev; 103 struct q_class_t *down, *up; 104}; 105 106struct syntax_t 107{ 108 int color; 109 int first; 110 int last; 111}; 112 113struct line_t 114{ 115 LOFF_T offset; 116 short type; 117 short continuation; 118 short chunks; 119 short search_cnt; 120 struct syntax_t *syntax; 121 struct syntax_t *search; 122 struct q_class_t *quote; 123 unsigned int is_cont_hdr; /* this line is a continuation of the previous header line */ 124}; 125 126#define ANSI_OFF (1<<0) 127#define ANSI_BLINK (1<<1) 128#define ANSI_BOLD (1<<2) 129#define ANSI_UNDERLINE (1<<3) 130#define ANSI_REVERSE (1<<4) 131#define ANSI_COLOR (1<<5) 132 133typedef struct _ansi_attr { 134 int attr; 135 int fg; 136 int bg; 137 int pair; 138} ansi_attr; 139 140static short InHelp = 0; 141 142#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) 143static struct resize { 144 int line; 145 int SearchCompiled; 146 int SearchBack; 147} *Resize = NULL; 148#endif 149 150#define NumSigLines 4 151 152static int check_sig (const char *s, struct line_t *info, int n) 153{ 154 int count = 0; 155 156 while (n > 0 && count <= NumSigLines) 157 { 158 if (info[n].type != MT_COLOR_SIGNATURE) 159 break; 160 count++; 161 n--; 162 } 163 164 if (count == 0) 165 return (-1); 166 167 if (count > NumSigLines) 168 { 169 /* check for a blank line */ 170 while (*s) 171 { 172 if (!ISSPACE (*s)) 173 return 0; 174 s++; 175 } 176 177 return (-1); 178 } 179 180 return (0); 181} 182 183static int 184comp_syntax_t (const void *m1, const void *m2) 185{ 186 const int *cnt = (const int *)m1; 187 const struct syntax_t *stx = (const struct syntax_t *)m2; 188 189 if (*cnt < stx->first) 190 return -1; 191 if (*cnt >= stx->last) 192 return 1; 193 return 0; 194} 195 196static void 197resolve_color (struct line_t *lineInfo, int n, int cnt, int flags, int special, 198 ansi_attr *a) 199{ 200 int def_color; /* color without syntax highlight */ 201 int color; /* final color */ 202 static int last_color; /* last color set */ 203 int search = 0, m; 204 struct syntax_t *matching_chunk; 205 206 if (!cnt) 207 last_color = -1; /* force attrset() */ 208 209 if (lineInfo[n].continuation) 210 { 211 if (!cnt && option (OPTMARKERS)) 212 { 213 SETCOLOR (MT_COLOR_MARKERS); 214 addch ('+'); 215 last_color = ColorDefs[MT_COLOR_MARKERS]; 216 } 217 m = (lineInfo[n].syntax)[0].first; 218 cnt += (lineInfo[n].syntax)[0].last; 219 } 220 else 221 m = n; 222 if (!(flags & MUTT_SHOWCOLOR)) 223 def_color = ColorDefs[MT_COLOR_NORMAL]; 224 else if (lineInfo[m].type == MT_COLOR_HEADER) 225 def_color = (lineInfo[m].syntax)[0].color; 226 else 227 def_color = ColorDefs[lineInfo[m].type]; 228 229 if ((flags & MUTT_SHOWCOLOR) && lineInfo[m].type == MT_COLOR_QUOTED) 230 { 231 struct q_class_t *class = lineInfo[m].quote; 232 233 if (class) 234 { 235 def_color = class->color; 236 237 while (class && class->length > cnt) 238 { 239 def_color = class->color; 240 class = class->up; 241 } 242 } 243 } 244 245 color = def_color; 246 if ((flags & MUTT_SHOWCOLOR) && lineInfo[m].chunks) 247 { 248 matching_chunk = bsearch (&cnt, lineInfo[m].syntax, lineInfo[m].chunks, 249 sizeof(struct syntax_t), comp_syntax_t); 250 if (matching_chunk && 251 (cnt >= matching_chunk->first) && 252 (cnt < matching_chunk->last)) 253 color = matching_chunk->color; 254 } 255 256 if ((flags & MUTT_SEARCH) && lineInfo[m].search_cnt) 257 { 258 matching_chunk = bsearch (&cnt, lineInfo[m].search, lineInfo[m].search_cnt, 259 sizeof(struct syntax_t), comp_syntax_t); 260 if (matching_chunk && 261 (cnt >= matching_chunk->first) && 262 (cnt < matching_chunk->last)) 263 { 264 color = ColorDefs[MT_COLOR_SEARCH]; 265 search = 1; 266 } 267 } 268 269 /* handle "special" bold & underlined characters */ 270 if (special || a->attr) 271 { 272#ifdef HAVE_COLOR 273 if ((a->attr & ANSI_COLOR)) 274 { 275 if (a->pair == -1) 276 a->pair = mutt_alloc_color (a->fg, a->bg); 277 color = a->pair; 278 if (a->attr & ANSI_BOLD) 279 color |= A_BOLD; 280 } 281 else 282#endif 283 if ((special & A_BOLD) || (a->attr & ANSI_BOLD)) 284 { 285 if (ColorDefs[MT_COLOR_BOLD] && !search) 286 color = ColorDefs[MT_COLOR_BOLD]; 287 else 288 color ^= A_BOLD; 289 } 290 if ((special & A_UNDERLINE) || (a->attr & ANSI_UNDERLINE)) 291 { 292 if (ColorDefs[MT_COLOR_UNDERLINE] && !search) 293 color = ColorDefs[MT_COLOR_UNDERLINE]; 294 else 295 color ^= A_UNDERLINE; 296 } 297 else if (a->attr & ANSI_REVERSE) 298 { 299 color ^= A_REVERSE; 300 } 301 else if (a->attr & ANSI_BLINK) 302 { 303 color ^= A_BLINK; 304 } 305 else if (a->attr == ANSI_OFF) 306 { 307 a->attr = 0; 308 } 309 } 310 311 if (color != last_color) 312 { 313 ATTRSET (color); 314 last_color = color; 315 } 316} 317 318static void 319append_line (struct line_t *lineInfo, int n, int cnt) 320{ 321 int m; 322 323 lineInfo[n+1].type = lineInfo[n].type; 324 (lineInfo[n+1].syntax)[0].color = (lineInfo[n].syntax)[0].color; 325 lineInfo[n+1].continuation = 1; 326 327 /* find the real start of the line */ 328 for (m = n; m >= 0; m--) 329 if (lineInfo[m].continuation == 0) break; 330 331 (lineInfo[n+1].syntax)[0].first = m; 332 (lineInfo[n+1].syntax)[0].last = (lineInfo[n].continuation) ? 333 cnt + (lineInfo[n].syntax)[0].last : cnt; 334} 335 336static void 337new_class_color (struct q_class_t *class, int *q_level) 338{ 339 class->index = (*q_level)++; 340 class->color = ColorQuote[class->index % ColorQuoteUsed]; 341} 342 343static void 344shift_class_colors (struct q_class_t *QuoteList, struct q_class_t *new_class, 345 int index, int *q_level) 346{ 347 struct q_class_t * q_list; 348 349 q_list = QuoteList; 350 new_class->index = -1; 351 352 while (q_list) 353 { 354 if (q_list->index >= index) 355 { 356 q_list->index++; 357 q_list->color = ColorQuote[q_list->index % ColorQuoteUsed]; 358 } 359 if (q_list->down) 360 q_list = q_list->down; 361 else if (q_list->next) 362 q_list = q_list->next; 363 else 364 { 365 while (!q_list->next) 366 { 367 q_list = q_list->up; 368 if (q_list == NULL) 369 break; 370 } 371 if (q_list) 372 q_list = q_list->next; 373 } 374 } 375 376 new_class->index = index; 377 new_class->color = ColorQuote[index % ColorQuoteUsed]; 378 (*q_level)++; 379} 380 381static void 382cleanup_quote (struct q_class_t **QuoteList) 383{ 384 struct q_class_t *ptr; 385 386 while (*QuoteList) 387 { 388 if ((*QuoteList)->down) 389 cleanup_quote (&((*QuoteList)->down)); 390 ptr = (*QuoteList)->next; 391 if ((*QuoteList)->prefix) 392 FREE (&(*QuoteList)->prefix); 393 FREE (QuoteList); /* __FREE_CHECKED__ */ 394 *QuoteList = ptr; 395 } 396 397 return; 398} 399 400static struct q_class_t * 401classify_quote (struct q_class_t **QuoteList, const char *qptr, 402 int length, int *force_redraw, int *q_level) 403{ 404 struct q_class_t *q_list = *QuoteList; 405 struct q_class_t *class = NULL, *tmp = NULL, *ptr, *save; 406 char *tail_qptr; 407 int offset, tail_lng; 408 int index = -1; 409 410 if (ColorQuoteUsed <= 1) 411 { 412 /* not much point in classifying quotes... */ 413 414 if (*QuoteList == NULL) 415 { 416 class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); 417 class->color = ColorQuote[0]; 418 *QuoteList = class; 419 } 420 return (*QuoteList); 421 } 422 423 /* Did I mention how much I like emulating Lisp in C? */ 424 425 /* classify quoting prefix */ 426 while (q_list) 427 { 428 if (length <= q_list->length) 429 { 430 /* case 1: check the top level nodes */ 431 432 if (mutt_strncmp (qptr, q_list->prefix, length) == 0) 433 { 434 if (length == q_list->length) 435 return q_list; /* same prefix: return the current class */ 436 437 /* found shorter prefix */ 438 if (tmp == NULL) 439 { 440 /* add a node above q_list */ 441 tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); 442 tmp->prefix = (char *) safe_calloc (1, length + 1); 443 strncpy (tmp->prefix, qptr, length); 444 tmp->length = length; 445 446 /* replace q_list by tmp in the top level list */ 447 if (q_list->next) 448 { 449 tmp->next = q_list->next; 450 q_list->next->prev = tmp; 451 } 452 if (q_list->prev) 453 { 454 tmp->prev = q_list->prev; 455 q_list->prev->next = tmp; 456 } 457 458 /* make q_list a child of tmp */ 459 tmp->down = q_list; 460 q_list->up = tmp; 461 462 /* q_list has no siblings for now */ 463 q_list->next = NULL; 464 q_list->prev = NULL; 465 466 /* update the root if necessary */ 467 if (q_list == *QuoteList) 468 *QuoteList = tmp; 469 470 index = q_list->index; 471 472 /* tmp should be the return class too */ 473 class = tmp; 474 475 /* next class to test; if tmp is a shorter prefix for another 476 * node, that node can only be in the top level list, so don't 477 * go down after this point 478 */ 479 q_list = tmp->next; 480 } 481 else 482 { 483 /* found another branch for which tmp is a shorter prefix */ 484 485 /* save the next sibling for later */ 486 save = q_list->next; 487 488 /* unlink q_list from the top level list */ 489 if (q_list->next) 490 q_list->next->prev = q_list->prev; 491 if (q_list->prev) 492 q_list->prev->next = q_list->next; 493 494 /* at this point, we have a tmp->down; link q_list to it */ 495 ptr = tmp->down; 496 /* sibling order is important here, q_list should be linked last */ 497 while (ptr->next) 498 ptr = ptr->next; 499 ptr->next = q_list; 500 q_list->next = NULL; 501 q_list->prev = ptr; 502 q_list->up = tmp; 503 504 index = q_list->index; 505 506 /* next class to test; as above, we shouldn't go down */ 507 q_list = save; 508 } 509 510 /* we found a shorter prefix, so certain quotes have changed classes */ 511 *force_redraw = 1; 512 continue; 513 } 514 else 515 { 516 /* shorter, but not a substring of the current class: try next */ 517 q_list = q_list->next; 518 continue; 519 } 520 } 521 else 522 { 523 /* case 2: try subclassing the current top level node */ 524 525 /* tmp != NULL means we already found a shorter prefix at case 1 */ 526 if (tmp == NULL && mutt_strncmp (qptr, q_list->prefix, q_list->length) == 0) 527 { 528 /* ok, it's a subclass somewhere on this branch */ 529 530 ptr = q_list; 531 offset = q_list->length; 532 533 q_list = q_list->down; 534 tail_lng = length - offset; 535 tail_qptr = (char *) qptr + offset; 536 537 while (q_list) 538 { 539 if (length <= q_list->length) 540 { 541 if (mutt_strncmp (tail_qptr, (q_list->prefix) + offset, tail_lng) == 0) 542 { 543 /* same prefix: return the current class */ 544 if (length == q_list->length) 545 return q_list; 546 547 /* found shorter common prefix */ 548 if (tmp == NULL) 549 { 550 /* add a node above q_list */ 551 tmp = (struct q_class_t *) 552 safe_calloc (1, sizeof (struct q_class_t)); 553 tmp->prefix = (char *) safe_calloc (1, length + 1); 554 strncpy (tmp->prefix, qptr, length); 555 tmp->length = length; 556 557 /* replace q_list by tmp */ 558 if (q_list->next) 559 { 560 tmp->next = q_list->next; 561 q_list->next->prev = tmp; 562 } 563 if (q_list->prev) 564 { 565 tmp->prev = q_list->prev; 566 q_list->prev->next = tmp; 567 } 568 569 /* make q_list a child of tmp */ 570 tmp->down = q_list; 571 tmp->up = q_list->up; 572 q_list->up = tmp; 573 if (tmp->up->down == q_list) 574 tmp->up->down = tmp; 575 576 /* q_list has no siblings */ 577 q_list->next = NULL; 578 q_list->prev = NULL; 579 580 index = q_list->index; 581 582 /* tmp should be the return class too */ 583 class = tmp; 584 585 /* next class to test */ 586 q_list = tmp->next; 587 } 588 else 589 { 590 /* found another branch for which tmp is a shorter prefix */ 591 592 /* save the next sibling for later */ 593 save = q_list->next; 594 595 /* unlink q_list from the top level list */ 596 if (q_list->next) 597 q_list->next->prev = q_list->prev; 598 if (q_list->prev) 599 q_list->prev->next = q_list->next; 600 601 /* at this point, we have a tmp->down; link q_list to it */ 602 ptr = tmp->down; 603 while (ptr->next) 604 ptr = ptr->next; 605 ptr->next = q_list; 606 q_list->next = NULL; 607 q_list->prev = ptr; 608 q_list->up = tmp; 609 610 index = q_list->index; 611 612 /* next class to test */ 613 q_list = save; 614 } 615 616 /* we found a shorter prefix, so we need a redraw */ 617 *force_redraw = 1; 618 continue; 619 } 620 else 621 { 622 q_list = q_list->next; 623 continue; 624 } 625 } 626 else 627 { 628 /* longer than the current prefix: try subclassing it */ 629 if (tmp == NULL && mutt_strncmp (tail_qptr, (q_list->prefix) + offset, 630 q_list->length - offset) == 0) 631 { 632 /* still a subclass: go down one level */ 633 ptr = q_list; 634 offset = q_list->length; 635 636 q_list = q_list->down; 637 tail_lng = length - offset; 638 tail_qptr = (char *) qptr + offset; 639 640 continue; 641 } 642 else 643 { 644 /* nope, try the next prefix */ 645 q_list = q_list->next; 646 continue; 647 } 648 } 649 } 650 651 /* still not found so far: add it as a sibling to the current node */ 652 if (class == NULL) 653 { 654 tmp = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); 655 tmp->prefix = (char *) safe_calloc (1, length + 1); 656 strncpy (tmp->prefix, qptr, length); 657 tmp->length = length; 658 659 if (ptr->down) 660 { 661 tmp->next = ptr->down; 662 ptr->down->prev = tmp; 663 } 664 ptr->down = tmp; 665 tmp->up = ptr; 666 667 new_class_color (tmp, q_level); 668 669 return tmp; 670 } 671 else 672 { 673 if (index != -1) 674 shift_class_colors (*QuoteList, tmp, index, q_level); 675 676 return class; 677 } 678 } 679 else 680 { 681 /* nope, try the next prefix */ 682 q_list = q_list->next; 683 continue; 684 } 685 } 686 } 687 688 if (class == NULL) 689 { 690 /* not found so far: add it as a top level class */ 691 class = (struct q_class_t *) safe_calloc (1, sizeof (struct q_class_t)); 692 class->prefix = (char *) safe_calloc (1, length + 1); 693 strncpy (class->prefix, qptr, length); 694 class->length = length; 695 new_class_color (class, q_level); 696 697 if (*QuoteList) 698 { 699 class->next = *QuoteList; 700 (*QuoteList)->prev = class; 701 } 702 *QuoteList = class; 703 } 704 705 if (index != -1) 706 shift_class_colors (*QuoteList, tmp, index, q_level); 707 708 return class; 709} 710 711static int brailleLine = -1; 712static int brailleCol = -1; 713 714static int check_attachment_marker (const char *); 715static int check_protected_header_marker (const char *); 716 717/* Checks if buf matches the QuoteRegexp and doesn't match Smileys. 718 * pmatch, if non-null, is populated with the regexec match against 719 * QuoteRegexp. This is used by the pager for calling classify_quote. 720 */ 721int 722mutt_is_quote_line (char *buf, regmatch_t *pmatch) 723{ 724 int is_quote = 0; 725 regmatch_t pmatch_internal[1], smatch[1]; 726 char c; 727 728 if (!pmatch) 729 pmatch = pmatch_internal; 730 731 if (QuoteRegexp.rx && 732 regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) 733 { 734 if (Smileys.rx && 735 regexec ((regex_t *) Smileys.rx, buf, 1, smatch, 0) == 0) 736 { 737 if (smatch[0].rm_so > 0) 738 { 739 c = buf[smatch[0].rm_so]; 740 buf[smatch[0].rm_so] = 0; 741 742 if (regexec ((regex_t *) QuoteRegexp.rx, buf, 1, pmatch, 0) == 0) 743 is_quote = 1; 744 745 buf[smatch[0].rm_so] = c; 746 } 747 } 748 else 749 is_quote = 1; 750 } 751 752 return is_quote; 753} 754 755static void 756resolve_types (char *buf, char *raw, struct line_t *lineInfo, int n, int last, 757 struct q_class_t **QuoteList, int *q_level, int *force_redraw, 758 int q_classify) 759{ 760 COLOR_LINE *color_line, *color_list; 761 regmatch_t pmatch[1]; 762 int found, offset, null_rx, i; 763 764 if (n == 0 || ISHEADER (lineInfo[n-1].type) || 765 (check_protected_header_marker (raw) == 0)) 766 { 767 if (buf[0] == '\n') /* end of header */ 768 { 769 lineInfo[n].type = MT_COLOR_NORMAL; 770 getyx(stdscr, brailleLine, brailleCol); 771 } 772 else 773 { 774 /* if this is a continuation of the previous line, use the previous 775 * line's color as default. */ 776 if (n > 0 && (buf[0] == ' ' || buf[0] == '\t')) 777 { 778 lineInfo[n].type = lineInfo[n-1].type; /* wrapped line */ 779 if (!option (OPTHEADERCOLORPARTIAL)) 780 { 781 (lineInfo[n].syntax)[0].color = (lineInfo[n-1].syntax)[0].color; 782 lineInfo[n].is_cont_hdr = 1; 783 } 784 } 785 else 786 { 787 lineInfo[n].type = MT_COLOR_HDEFAULT; 788 } 789 790 /* When this option is unset, we color the entire header the 791 * same color. Otherwise, we handle the header patterns just 792 * like body patterns (further below). 793 */ 794 if (!option (OPTHEADERCOLORPARTIAL)) 795 { 796 for (color_line = ColorHdrList; color_line; color_line = color_line->next) 797 { 798 if (REGEXEC (color_line->rx, buf) == 0) 799 { 800 lineInfo[n].type = MT_COLOR_HEADER; 801 lineInfo[n].syntax[0].color = color_line->pair; 802 if (lineInfo[n].is_cont_hdr) 803 { 804 /* adjust the previous continuation lines to reflect the color of this continuation line */ 805 int j; 806 for (j = n - 1; j >= 0 && lineInfo[j].is_cont_hdr; --j) 807 { 808 lineInfo[j].type = lineInfo[n].type; 809 lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color; 810 } 811 /* now adjust the first line of this header field */ 812 if (j >= 0) 813 { 814 lineInfo[j].type = lineInfo[n].type; 815 lineInfo[j].syntax[0].color = lineInfo[n].syntax[0].color; 816 } 817 *force_redraw = 1; /* the previous lines have already been drawn on the screen */ 818 } 819 break; 820 } 821 } 822 } 823 } 824 } 825 else if (mutt_strncmp ("\033[0m", raw, 4) == 0) /* a little hack... */ 826 lineInfo[n].type = MT_COLOR_NORMAL; 827 else if (check_attachment_marker ((char *) raw) == 0) 828 lineInfo[n].type = MT_COLOR_ATTACHMENT; 829 else if (mutt_strcmp ("-- \n", buf) == 0 || mutt_strcmp ("-- \r\n", buf) == 0) 830 { 831 i = n + 1; 832 833 lineInfo[n].type = MT_COLOR_SIGNATURE; 834 while (i < last && check_sig (buf, lineInfo, i - 1) == 0 && 835 (lineInfo[i].type == MT_COLOR_NORMAL || 836 lineInfo[i].type == MT_COLOR_QUOTED || 837 lineInfo[i].type == MT_COLOR_HEADER)) 838 { 839 /* oops... */ 840 if (lineInfo[i].chunks) 841 { 842 lineInfo[i].chunks = 0; 843 safe_realloc (&(lineInfo[n].syntax), 844 sizeof (struct syntax_t)); 845 } 846 lineInfo[i++].type = MT_COLOR_SIGNATURE; 847 } 848 } 849 else if (check_sig (buf, lineInfo, n - 1) == 0) 850 lineInfo[n].type = MT_COLOR_SIGNATURE; 851 else if (mutt_is_quote_line (buf, pmatch)) 852 { 853 if (q_classify && lineInfo[n].quote == NULL) 854 lineInfo[n].quote = classify_quote (QuoteList, buf + pmatch[0].rm_so, 855 pmatch[0].rm_eo - pmatch[0].rm_so, 856 force_redraw, q_level); 857 lineInfo[n].type = MT_COLOR_QUOTED; 858 } 859 else 860 lineInfo[n].type = MT_COLOR_NORMAL; 861 862 /* body patterns */ 863 if (lineInfo[n].type == MT_COLOR_NORMAL || 864 lineInfo[n].type == MT_COLOR_QUOTED || 865 (lineInfo[n].type == MT_COLOR_HDEFAULT && option (OPTHEADERCOLORPARTIAL))) 866 { 867 size_t nl; 868 869 /* don't consider line endings part of the buffer 870 * for regex matching */ 871 if ((nl = mutt_strlen (buf)) > 0 && buf[nl-1] == '\n') 872 buf[nl-1] = 0; 873 874 i = 0; 875 offset = 0; 876 lineInfo[n].chunks = 0; 877 if (lineInfo[n].type == MT_COLOR_HDEFAULT) 878 color_list = ColorHdrList; 879 else 880 color_list = ColorBodyList; 881 color_line = color_list; 882 while (color_line) 883 { 884 color_line->stop_matching = 0; 885 color_line = color_line->next; 886 } 887 do 888 { 889 if (!buf[offset]) 890 break; 891 892 found = 0; 893 null_rx = 0; 894 color_line = color_list; 895 while (color_line) 896 { 897 if (!color_line->stop_matching && 898 regexec (&color_line->rx, buf + offset, 1, pmatch, 899 (offset ? REG_NOTBOL : 0)) == 0) 900 { 901 if (pmatch[0].rm_eo != pmatch[0].rm_so) 902 { 903 if (!found) 904 { 905 /* Abort if we fill up chunks. 906 * Yes, this really happened. See #3888 */ 907 if (lineInfo[n].chunks == SHRT_MAX) 908 { 909 null_rx = 0; 910 break; 911 } 912 if (++(lineInfo[n].chunks) > 1) 913 safe_realloc (&(lineInfo[n].syntax), 914 (lineInfo[n].chunks) * sizeof (struct syntax_t)); 915 } 916 i = lineInfo[n].chunks - 1; 917 pmatch[0].rm_so += offset; 918 pmatch[0].rm_eo += offset; 919 if (!found || 920 pmatch[0].rm_so < (lineInfo[n].syntax)[i].first || 921 (pmatch[0].rm_so == (lineInfo[n].syntax)[i].first && 922 pmatch[0].rm_eo > (lineInfo[n].syntax)[i].last)) 923 { 924 (lineInfo[n].syntax)[i].color = color_line->pair; 925 (lineInfo[n].syntax)[i].first = pmatch[0].rm_so; 926 (lineInfo[n].syntax)[i].last = pmatch[0].rm_eo; 927 } 928 found = 1; 929 null_rx = 0; 930 } 931 else 932 null_rx = 1; /* empty regexp; don't add it, but keep looking */ 933 } 934 /* Once a regexp fails to match, don't try matching it again. 935 * On very long lines this can cause a performance issue if there 936 * are other regexps that have many matches. */ 937 else 938 color_line->stop_matching = 1; 939 color_line = color_line->next; 940 } 941 942 if (null_rx) 943 offset++; /* avoid degenerate cases */ 944 else 945 offset = (lineInfo[n].syntax)[i].last; 946 } while (found || null_rx); 947 if (nl > 0) 948 buf[nl] = '\n'; 949 } 950} 951 952static int is_ansi (unsigned char *buf) 953{ 954 while (*buf && (isdigit(*buf) || *buf == ';')) 955 buf++; 956 return (*buf == 'm'); 957} 958 959static int check_marker (const char *q, const char *p) 960{ 961 for (;*p == *q && *q && *p && *q != '\a' && *p != '\a'; p++, q++) 962 ; 963 return (int) (*p - *q); 964} 965 966static int check_attachment_marker (const char *p) 967{ 968 return check_marker (AttachmentMarker, p); 969} 970 971static int check_protected_header_marker (const char *p) 972{ 973 return check_marker (ProtectedHeaderMarker, p); 974} 975 976static int grok_ansi(unsigned char *buf, int pos, ansi_attr *a) 977{ 978 int x = pos; 979 980 while (isdigit(buf[x]) || buf[x] == ';') 981 x++; 982 983 /* Character Attributes */ 984 if (option (OPTALLOWANSI) && a != NULL && buf[x] == 'm') 985 { 986 if (pos == x) 987 { 988#ifdef HAVE_COLOR 989 if (a->pair != -1) 990 mutt_free_color (a->fg, a->bg); 991#endif 992 a->attr = ANSI_OFF; 993 a->pair = -1; 994 } 995 while (pos < x) 996 { 997 if (buf[pos] == '1' && (pos+1 == x || buf[pos+1] == ';')) 998 { 999 a->attr |= ANSI_BOLD; 1000 pos += 2; 1001 } 1002 else if (buf[pos] == '4' && (pos+1 == x || buf[pos+1] == ';')) 1003 { 1004 a->attr |= ANSI_UNDERLINE; 1005 pos += 2; 1006 } 1007 else if (buf[pos] == '5' && (pos+1 == x || buf[pos+1] == ';')) 1008 { 1009 a->attr |= ANSI_BLINK; 1010 pos += 2; 1011 } 1012 else if (buf[pos] == '7' && (pos+1 == x || buf[pos+1] == ';')) 1013 { 1014 a->attr |= ANSI_REVERSE; 1015 pos += 2; 1016 } 1017 else if (buf[pos] == '0' && (pos+1 == x || buf[pos+1] == ';')) 1018 { 1019#ifdef HAVE_COLOR 1020 if (a->pair != -1) 1021 mutt_free_color(a->fg,a->bg); 1022#endif 1023 a->attr = ANSI_OFF; 1024 a->pair = -1; 1025 pos += 2; 1026 } 1027 else if (buf[pos] == '3' && isdigit(buf[pos+1])) 1028 { 1029#ifdef HAVE_COLOR 1030 if (a->pair != -1) 1031 mutt_free_color(a->fg,a->bg); 1032#endif 1033 a->pair = -1; 1034 a->attr |= ANSI_COLOR; 1035 a->fg = buf[pos+1] - '0'; 1036 pos += 3; 1037 } 1038 else if (buf[pos] == '4' && isdigit(buf[pos+1])) 1039 { 1040#ifdef HAVE_COLOR 1041 if (a->pair != -1) 1042 mutt_free_color(a->fg,a->bg); 1043#endif 1044 a->pair = -1; 1045 a->attr |= ANSI_COLOR; 1046 a->bg = buf[pos+1] - '0'; 1047 pos += 3; 1048 } 1049 else 1050 { 1051 while (pos < x && buf[pos] != ';') pos++; 1052 pos++; 1053 } 1054 } 1055 } 1056 pos = x; 1057 return pos; 1058} 1059 1060static int 1061fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf, 1062 unsigned char **fmt, size_t *blen, int *buf_ready) 1063{ 1064 unsigned char *p, *q; 1065 static int b_read; 1066 int l; 1067 1068 if (*buf_ready == 0) 1069 { 1070 if (offset != *last_pos) 1071 fseeko (f, offset, 0); 1072 if ((*buf = (unsigned char *) mutt_read_line ((char *) *buf, blen, f, &l, MUTT_EOL)) == NULL) 1073 { 1074 fmt[0] = 0; 1075 return (-1); 1076 } 1077 *last_pos = ftello (f); 1078 b_read = (int) (*last_pos - offset); 1079 *buf_ready = 1; 1080 1081 safe_realloc (fmt, *blen); 1082 1083 /* copy "buf" to "fmt", but without bold and underline controls */ 1084 p = *buf; 1085 q = *fmt; 1086 while (*p) 1087 { 1088 if (*p == '\010' && (p > *buf)) 1089 { 1090 if (*(p+1) == '_') /* underline */ 1091 p += 2; 1092 else if (*(p+1) && q > *fmt) /* bold or overstrike */ 1093 { 1094 *(q-1) = *(p+1); 1095 p += 2; 1096 } 1097 else /* ^H */ 1098 *q++ = *p++; 1099 } 1100 else if (*p == '\033' && *(p+1) == '[' && is_ansi (p + 2)) 1101 { 1102 while (*p++ != 'm') /* skip ANSI sequence */ 1103 ; 1104 } 1105 else if (*p == '\033' && *(p+1) == ']' && 1106 ((check_attachment_marker ((char *) p) == 0) || 1107 (check_protected_header_marker ((char *) p) == 0))) 1108 { 1109 dprint (2, (debugfile, "fill_buffer: Seen attachment marker.\n")); 1110 while (*p++ != '\a') /* skip pseudo-ANSI sequence */ 1111 ; 1112 } 1113 else 1114 *q++ = *p++; 1115 } 1116 *q = 0; 1117 } 1118 return b_read; 1119} 1120 1121 1122static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, 1123 int flags, ansi_attr *pa, int cnt, 1124 int *pspace, int *pvch, int *pcol, int *pspecial, 1125 mutt_window_t *pager_window) 1126{ 1127 int space = -1; /* index of the last space or TAB */ 1128 int col = option (OPTMARKERS) ? (*lineInfo)[n].continuation : 0; 1129 size_t k; 1130 int ch, vch, last_special = -1, special = 0, t; 1131 wchar_t wc; 1132 mbstate_t mbstate; 1133 int wrap_cols = mutt_window_wrap_cols (pager_window, (flags & MUTT_PAGER_NOWRAP) ? 0 : Wrap); 1134 1135 if (check_attachment_marker ((char *)buf) == 0) 1136 wrap_cols = pager_window->cols; 1137 1138 /* FIXME: this should come from lineInfo */ 1139 memset(&mbstate, 0, sizeof(mbstate)); 1140 1141 for (ch = 0, vch = 0; ch < cnt; ch += k, vch += k) 1142 { 1143 /* Handle ANSI sequences */ 1144 while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == '[' && 1145 is_ansi (buf+ch+2)) 1146 ch = grok_ansi (buf, ch+2, pa) + 1; 1147 1148 while (cnt-ch >= 2 && buf[ch] == '\033' && buf[ch+1] == ']' && 1149 ((check_attachment_marker ((char *) buf+ch) == 0) || 1150 (check_protected_header_marker ((char *) buf+ch) == 0))) 1151 { 1152 while (buf[ch++] != '\a') 1153 if (ch >= cnt) 1154 break; 1155 } 1156 1157 /* is anything left to do? */ 1158 if (ch >= cnt) 1159 break; 1160 1161 k = mbrtowc (&wc, (char *)buf+ch, cnt-ch, &mbstate); 1162 if (k == (size_t)(-2) || k == (size_t)(-1)) 1163 { 1164 if (k == (size_t)(-1)) 1165 memset(&mbstate, 0, sizeof(mbstate)); 1166 dprint (1, (debugfile, "%s:%d: mbrtowc returned %d; errno = %d.\n", 1167 __FILE__, __LINE__, k, errno)); 1168 if (col + 4 > wrap_cols) 1169 break; 1170 col += 4; 1171 if (pa) 1172 printw ("\\%03o", buf[ch]); 1173 k = 1; 1174 continue; 1175 } 1176 if (k == 0) 1177 k = 1; 1178 1179 if (Charset_is_utf8) 1180 { 1181 if (wc == 0x200B || wc == 0xFEFF) 1182 { 1183 dprint (3, (debugfile, "skip zero-width character U+%04X\n", (unsigned short)wc)); 1184 continue; 1185 } 1186 if (is_display_corrupting_utf8 (wc)) 1187 { 1188 dprint (3, (debugfile, "filtered U+%04X\n", (unsigned short)wc)); 1189 continue; 1190 } 1191 } 1192 1193 /* Handle backspace */ 1194 special = 0; 1195 if (IsWPrint (wc)) 1196 { 1197 wchar_t wc1; 1198 mbstate_t mbstate1; 1199 size_t k1, k2; 1200 1201 mbstate1 = mbstate; 1202 k1 = mbrtowc (&wc1, (char *)buf+ch+k, cnt-ch-k, &mbstate1); 1203 while ((k1 != (size_t)(-2)) && (k1 != (size_t)(-1)) && 1204 (k1 > 0) && (wc1 == '\b')) 1205 { 1206 k2 = mbrtowc (&wc1, (char *)buf+ch+k+k1, cnt-ch-k-k1, &mbstate1); 1207 if ((k2 == (size_t)(-2)) || (k2 == (size_t)(-1)) || 1208 (k2 == 0) || (!IsWPrint (wc1))) 1209 break; 1210 1211 if (wc == wc1) 1212 { 1213 special |= (wc == '_' && special & A_UNDERLINE) 1214 ? A_UNDERLINE : A_BOLD; 1215 } 1216 else if (wc == '_' || wc1 == '_') 1217 { 1218 special |= A_UNDERLINE; 1219 wc = (wc1 == '_') ? wc : wc1; 1220 } 1221 else 1222 { 1223 /* special = 0; / * overstrike: nothing to do! */ 1224 wc = wc1; 1225 } 1226 1227 ch += k + k1; 1228 k = k2; 1229 mbstate = mbstate1; 1230 k1 = mbrtowc (&wc1, (char *)buf+ch+k, cnt-ch-k, &mbstate1); 1231 } 1232 } 1233 1234 if (pa && 1235 ((flags & (MUTT_SHOWCOLOR | MUTT_SEARCH | MUTT_PAGER_MARKER)) || 1236 special || last_special || pa->attr)) 1237 { 1238 resolve_color (*lineInfo, n, vch, flags, special, pa); 1239 last_special = special; 1240 } 1241 1242 if (IsWPrint (wc) || (Charset_is_utf8 && wc == 0x00A0)) 1243 { 1244 if (wc == ' ') 1245 space = ch; 1246 t = wcwidth (wc); 1247 if (col + t > wrap_cols) 1248 break; 1249 col += t; 1250 if (pa) 1251 mutt_addwch (wc); 1252 } 1253 else if (wc == '\n') 1254 break; 1255 else if (wc == '\t') 1256 { 1257 space = ch; 1258 t = (col & ~7) + 8; 1259 if (t > wrap_cols) 1260 break; 1261 if (pa) 1262 for (; col < t; col++) 1263 addch (' '); 1264 else 1265 col = t; 1266 } 1267 else if (wc < 0x20 || wc == 0x7f) 1268 { 1269 if (col + 2 > wrap_cols) 1270 break; 1271 col += 2; 1272 if (pa) 1273 printw ("^%c", ('@' + wc) & 0x7f); 1274 } 1275 else if (wc < 0x100) 1276 { 1277 if (col + 4 > wrap_cols) 1278 break; 1279 col += 4; 1280 if (pa) 1281 printw ("\\%03o", wc); 1282 } 1283 else 1284 { 1285 if (col + 1 > wrap_cols) 1286 break; 1287 ++col; 1288 if (pa) 1289 mutt_addwch (replacement_char ()); 1290 } 1291 } 1292 *pspace = space; 1293 *pcol = col; 1294 *pvch = vch; 1295 *pspecial = special; 1296 return ch; 1297} 1298 1299/* 1300 * Args: 1301 * flags MUTT_SHOWFLAT, show characters (used for displaying help) 1302 * MUTT_SHOWCOLOR, show characters in color 1303 * otherwise don't show characters 1304 * MUTT_HIDE, don't show quoted text 1305 * MUTT_SEARCH, resolve search patterns 1306 * MUTT_TYPES, compute line's type 1307 * MUTT_PAGER_NSKIP, keeps leading whitespace 1308 * MUTT_PAGER_MARKER, eventually show markers 1309 * 1310 * Return values: 1311 * -1 EOF was reached 1312 * 0 normal exit, line was not displayed 1313 * >0 normal exit, line was displayed 1314 */ 1315 1316static int 1317display_line (FILE *f, LOFF_T *last_pos, struct line_t **lineInfo, int n, 1318 int *last, int *max, int flags, struct q_class_t **QuoteList, 1319 int *q_level, int *force_redraw, regex_t *SearchRE, 1320 mutt_window_t *pager_window) 1321{ 1322 unsigned char *buf = NULL, *fmt = NULL; 1323 size_t buflen = 0; 1324 unsigned char *buf_ptr = buf; 1325 int ch, vch, col, cnt, b_read; 1326 int buf_ready = 0, change_last = 0; 1327 int special; 1328 int offset; 1329 int def_color; 1330 int m; 1331 int rc = -1; 1332 ansi_attr a = {0,0,0,-1}; 1333 regmatch_t pmatch[1]; 1334 1335 if (n == *last) 1336 { 1337 (*last)++; 1338 change_last = 1; 1339 } 1340 1341 if (*last == *max) 1342 { 1343 safe_realloc (lineInfo, sizeof (struct line_t) * (*max += LINES)); 1344 for (ch = *last; ch < *max ; ch++) 1345 { 1346 memset (&((*lineInfo)[ch]), 0, sizeof (struct line_t)); 1347 (*lineInfo)[ch].type = -1; 1348 (*lineInfo)[ch].search_cnt = -1; 1349 (*lineInfo)[ch].syntax = safe_malloc (sizeof (struct syntax_t)); 1350 ((*lineInfo)[ch].syntax)[0].first = ((*lineInfo)[ch].syntax)[0].last = -1; 1351 } 1352 } 1353 1354 /* only do color hiliting if we are viewing a message */ 1355 if (flags & (MUTT_SHOWCOLOR | MUTT_TYPES)) 1356 { 1357 if ((*lineInfo)[n].type == -1) 1358 { 1359 /* determine the line class */ 1360 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0) 1361 { 1362 if (change_last) 1363 (*last)--; 1364 goto out; 1365 } 1366 1367 resolve_types ((char *) fmt, (char *) buf, *lineInfo, n, *last, 1368 QuoteList, q_level, force_redraw, flags & MUTT_SHOWCOLOR); 1369 1370 /* avoid race condition for continuation lines when scrolling up */ 1371 for (m = n + 1; m < *last && (*lineInfo)[m].offset && (*lineInfo)[m].continuation; m++) 1372 (*lineInfo)[m].type = (*lineInfo)[n].type; 1373 } 1374 1375 /* this also prevents searching through the hidden lines */ 1376 if ((flags & MUTT_HIDE) && (*lineInfo)[n].type == MT_COLOR_QUOTED) 1377 flags = 0; /* MUTT_NOSHOW */ 1378 } 1379 1380 /* At this point, (*lineInfo[n]).quote may still be undefined. We 1381 * don't want to compute it every time MUTT_TYPES is set, since this 1382 * would slow down the "bottom" function unacceptably. A compromise 1383 * solution is hence to call regexec() again, just to find out the 1384 * length of the quote prefix. 1385 */ 1386 if ((flags & MUTT_SHOWCOLOR) && !(*lineInfo)[n].continuation && 1387 (*lineInfo)[n].type == MT_COLOR_QUOTED && (*lineInfo)[n].quote == NULL) 1388 { 1389 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0) 1390 { 1391 if (change_last) 1392 (*last)--; 1393 goto out; 1394 } 1395 regexec ((regex_t *) QuoteRegexp.rx, (char *) fmt, 1, pmatch, 0); 1396 (*lineInfo)[n].quote = 1397 classify_quote (QuoteList, 1398 (char *) fmt + pmatch[0].rm_so, 1399 pmatch[0].rm_eo - pmatch[0].rm_so, 1400 force_redraw, q_level); 1401 } 1402 1403 if ((flags & MUTT_SEARCH) && !(*lineInfo)[n].continuation && (*lineInfo)[n].search_cnt == -1) 1404 { 1405 if (fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, &buflen, &buf_ready) < 0) 1406 { 1407 if (change_last) 1408 (*last)--; 1409 goto out; 1410 } 1411 1412 offset = 0; 1413 (*lineInfo)[n].search_cnt = 0; 1414 while (regexec (SearchRE, (char *) fmt + offset, 1, pmatch, (offset ? REG_NOTBOL : 0)) == 0) 1415 { 1416 if (++((*lineInfo)[n].search_cnt) > 1) 1417 safe_realloc (&((*lineInfo)[n].search), 1418 ((*lineInfo)[n].search_cnt) * sizeof (struct syntax_t)); 1419 else 1420 (*lineInfo)[n].search = safe_malloc (sizeof (struct syntax_t)); 1421 pmatch[0].rm_so += offset; 1422 pmatch[0].rm_eo += offset; 1423 ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].first = pmatch[0].rm_so; 1424 ((*lineInfo)[n].search)[(*lineInfo)[n].search_cnt - 1].last = pmatch[0].rm_eo; 1425 1426 if (pmatch[0].rm_eo == pmatch[0].rm_so) 1427 offset++; /* avoid degenerate cases */ 1428 else 1429 offset = pmatch[0].rm_eo; 1430 if (!fmt[offset]) 1431 break; 1432 } 1433 } 1434 1435 if (!(flags & MUTT_SHOW) && (*lineInfo)[n+1].offset > 0) 1436 { 1437 /* we've already scanned this line, so just exit */ 1438 rc = 0; 1439 goto out; 1440 } 1441 if ((flags & MUTT_SHOWCOLOR) && *force_redraw && (*lineInfo)[n+1].offset > 0) 1442 { 1443 /* no need to try to display this line... */ 1444 rc = 1; 1445 goto out; /* fake display */ 1446 } 1447 1448 if ((b_read = fill_buffer (f, last_pos, (*lineInfo)[n].offset, &buf, &fmt, 1449 &buflen, &buf_ready)) < 0) 1450 { 1451 if (change_last) 1452 (*last)--; 1453 goto out; 1454 } 1455 1456 /* now chose a good place to break the line */ 1457 cnt = format_line (lineInfo, n, buf, flags, 0, b_read, &ch, &vch, &col, &special, 1458 pager_window); 1459 buf_ptr = buf + cnt; 1460 1461 /* move the break point only if smart_wrap is set */ 1462 if (option (OPTWRAP)) 1463 { 1464 if (cnt < b_read && 1465 ch != -1 && 1466 buf[cnt] != ' ' && buf[cnt] != '\t' && buf[cnt] != '\n' && buf[cnt] != '\r') 1467 { 1468 buf_ptr = buf + ch; 1469 /* skip trailing blanks */ 1470 while (ch && (buf[ch] == ' ' || buf[ch] == '\t' || buf[ch] == '\r')) 1471 ch--; 1472 /* A very long word with leading spaces causes infinite 1473 * wrapping when MUTT_PAGER_NSKIP is set. A folded header 1474 * with a single long word shouldn't be smartwrapped 1475 * either. So just disable smart_wrap if it would wrap at the 1476 * beginning of the line. */ 1477 if (!ch) 1478 buf_ptr = buf + cnt; 1479 else 1480 cnt = ch + 1; 1481 } 1482 if (!(flags & MUTT_PAGER_NSKIP)) 1483 /* skip leading blanks on the next line too */ 1484 while (*buf_ptr == ' ' || *buf_ptr == '\t') 1485 buf_ptr++; 1486 } 1487 1488 if (*buf_ptr == '\r') 1489 buf_ptr++; 1490 if (*buf_ptr == '\n') 1491 buf_ptr++; 1492 1493 if ((int) (buf_ptr - buf) < b_read && !(*lineInfo)[n+1].continuation) 1494 append_line (*lineInfo, n, (int) (buf_ptr - buf)); 1495 (*lineInfo)[n+1].offset = (*lineInfo)[n].offset + (long) (buf_ptr - buf); 1496 1497 /* if we don't need to display the line we are done */ 1498 if (!(flags & MUTT_SHOW)) 1499 { 1500 rc = 0; 1501 goto out; 1502 } 1503 1504 /* display the line */ 1505 format_line (lineInfo, n, buf, flags, &a, cnt, &ch, &vch, &col, &special, 1506 pager_window); 1507 1508 /* avoid a bug in ncurses... */ 1509#ifndef USE_SLANG_CURSES 1510 if (col == 0) 1511 { 1512 NORMAL_COLOR; 1513 addch (' '); 1514 } 1515#endif 1516 1517 /* end the last color pattern (needed by S-Lang) */ 1518 if (special || (col != pager_window->cols && (flags & (MUTT_SHOWCOLOR | MUTT_SEARCH)))) 1519 resolve_color (*lineInfo, n, vch, flags, 0, &a); 1520 1521 /* 1522 * Fill the blank space at the end of the line with the prevailing color. 1523 * ncurses does an implicit clrtoeol() when you do addch('\n') so we have 1524 * to make sure to reset the color *after* that 1525 */ 1526 if (flags & MUTT_SHOWCOLOR) 1527 { 1528 m = ((*lineInfo)[n].continuation) ? ((*lineInfo)[n].syntax)[0].first : n; 1529 if ((*lineInfo)[m].type == MT_COLOR_HEADER) 1530 def_color = ((*lineInfo)[m].syntax)[0].color; 1531 else 1532 def_color = ColorDefs[ (*lineInfo)[m].type ]; 1533 1534 ATTRSET(def_color); 1535 } 1536 1537 if (col < pager_window->cols) 1538 mutt_window_clrtoeol (pager_window); 1539 1540 /* 1541 * reset the color back to normal. This *must* come after the 1542 * clrtoeol, otherwise the color for this line will not be 1543 * filled to the right margin. 1544 */ 1545 if (flags & MUTT_SHOWCOLOR) 1546 NORMAL_COLOR; 1547 1548 /* build a return code */ 1549 if (!(flags & MUTT_SHOW)) 1550 flags = 0; 1551 1552 rc = flags; 1553 1554out: 1555 FREE(&buf); 1556 FREE(&fmt); 1557 return rc; 1558} 1559 1560static int 1561upNLines (int nlines, struct line_t *info, int cur, int hiding) 1562{ 1563 while (cur > 0 && nlines > 0) 1564 { 1565 cur--; 1566 if (!hiding || info[cur].type != MT_COLOR_QUOTED) 1567 nlines--; 1568 } 1569 1570 return cur; 1571} 1572 1573static const struct mapping_t PagerHelp[] = { 1574 { N_("Exit"), OP_EXIT }, 1575 { N_("PrevPg"), OP_PREV_PAGE }, 1576 { N_("NextPg"), OP_NEXT_PAGE }, 1577 { NULL, 0 } 1578}; 1579static const struct mapping_t PagerHelpExtra[] = { 1580 { N_("View Attachm."), OP_VIEW_ATTACHMENTS }, 1581 { N_("Del"), OP_DELETE }, 1582 { N_("Reply"), OP_REPLY }, 1583 { N_("Next"), OP_MAIN_NEXT_UNDELETED }, 1584 { NULL, 0 } 1585}; 1586 1587void mutt_clear_pager_position (void) 1588{ 1589 TopLine = 0; 1590 OldHdr = NULL; 1591} 1592 1593typedef struct 1594{ 1595 int flags; 1596 pager_t *extra; 1597 int indexlen; 1598 int indicator; /* the indicator line of the PI */ 1599 int oldtopline; 1600 int lines; 1601 int maxLine; 1602 int lastLine; 1603 int curline; 1604 int topline; 1605 int force_redraw; 1606 int has_types; 1607 int hideQuoted; 1608 int q_level; 1609 struct q_class_t *QuoteList; 1610 LOFF_T last_pos; 1611 LOFF_T last_offset; 1612 mutt_window_t *index_status_window; 1613 mutt_window_t *index_window; 1614 mutt_window_t *pager_status_window; 1615 mutt_window_t *pager_window; 1616 MUTTMENU *index; /* the Pager Index (PI) */ 1617 regex_t SearchRE; 1618 int SearchCompiled; 1619 int SearchFlag; 1620 int SearchBack; 1621 const char *banner; 1622 const char *helpstr; 1623 char *searchbuf; 1624 struct line_t *lineInfo; 1625 FILE *fp; 1626 struct stat sb; 1627} pager_redraw_data_t; 1628 1629static void pager_menu_redraw (MUTTMENU *pager_menu) 1630{ 1631 pager_redraw_data_t *rd = pager_menu->redraw_data; 1632 int i, j, err; 1633 char buffer[LONG_STRING]; 1634 1635 if (!rd) 1636 return; 1637 1638 if (pager_menu->redraw & REDRAW_FULL) 1639 { 1640#if ! (defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM)) 1641 mutt_reflow_windows (); 1642#endif 1643 NORMAL_COLOR; 1644 /* clear() doesn't optimize screen redraws */ 1645 move (0, 0); 1646 clrtobot (); 1647 1648 if (IsHeader (rd->extra) && Context->vcount + 1 < PagerIndexLines) 1649 rd->indexlen = Context->vcount + 1; 1650 else 1651 rd->indexlen = PagerIndexLines; 1652 1653 rd->indicator = rd->indexlen / 3; 1654 1655 memcpy (rd->pager_window, MuttIndexWindow, sizeof(mutt_window_t)); 1656 memcpy (rd->pager_status_window, MuttStatusWindow, sizeof(mutt_window_t)); 1657 rd->index_status_window->rows = rd->index_window->rows = 0; 1658 1659 if (IsHeader (rd->extra) && PagerIndexLines) 1660 { 1661 memcpy (rd->index_window, MuttIndexWindow, sizeof(mutt_window_t)); 1662 rd->index_window->rows = rd->indexlen > 0 ? rd->indexlen - 1 : 0; 1663 1664 if (option (OPTSTATUSONTOP)) 1665 { 1666 memcpy (rd->index_status_window, MuttStatusWindow, sizeof(mutt_window_t)); 1667 1668 memcpy (rd->pager_status_window, MuttIndexWindow, sizeof(mutt_window_t)); 1669 rd->pager_status_window->rows = 1; 1670 rd->pager_status_window->row_offset += rd->index_window->rows; 1671 1672 rd->pager_window->rows -= rd->index_window->rows + rd->pager_status_window->rows; 1673 rd->pager_window->row_offset += rd->index_window->rows + rd->pager_status_window->rows; 1674 } 1675 else 1676 { 1677 memcpy (rd->index_status_window, MuttIndexWindow, sizeof(mutt_window_t)); 1678 rd->index_status_window->rows = 1; 1679 rd->index_status_window->row_offset += rd->index_window->rows; 1680 1681 rd->pager_window->rows -= rd->index_window->rows + rd->index_status_window->rows; 1682 rd->pager_window->row_offset += rd->index_window->rows + rd->index_status_window->rows; 1683 } 1684 } 1685 1686 if (option (OPTHELP)) 1687 { 1688 SETCOLOR (MT_COLOR_STATUS); 1689 mutt_window_move (MuttHelpWindow, 0, 0); 1690 mutt_paddstr (MuttHelpWindow->cols, rd->helpstr); 1691 NORMAL_COLOR; 1692 } 1693 1694#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) 1695 if (Resize != NULL) 1696 { 1697 if ((rd->SearchCompiled = Resize->SearchCompiled)) 1698 { 1699 if ((err = REGCOMP (&rd->SearchRE, rd->searchbuf, 1700 REG_NEWLINE | mutt_which_case (rd->searchbuf))) != 0) 1701 { 1702 regerror (err, &rd->SearchRE, buffer, sizeof (buffer)); 1703 mutt_error ("%s", buffer); 1704 rd->SearchCompiled = 0; 1705 } 1706 else 1707 { 1708 rd->SearchFlag = MUTT_SEARCH; 1709 rd->SearchBack = Resize->SearchBack; 1710 } 1711 } 1712 rd->lines = Resize->line; 1713 pager_menu->redraw |= REDRAW_FLOW; 1714 1715 FREE (&Resize); 1716 } 1717#endif 1718 1719 if (IsHeader (rd->extra) && PagerIndexLines) 1720 { 1721 if (rd->index == NULL) 1722 { 1723 /* only allocate the space if/when we need the index. 1724 Initialise the menu as per the main index */ 1725 rd->index = mutt_new_menu(MENU_MAIN); 1726 rd->index->make_entry = index_make_entry; 1727 rd->index->color = index_color; 1728 rd->index->max = Context->vcount; 1729 rd->index->current = rd->extra->hdr->virtual; 1730 rd->index->indexwin = rd->index_window; 1731 rd->index->statuswin = rd->index_status_window; 1732 } 1733 1734 NORMAL_COLOR; 1735 rd->index->pagelen = rd->index_window->rows;; 1736 1737 /* some fudge to work out where abouts the indicator should go */ 1738 if (rd->index->current - rd->indicator < 0) 1739 rd->index->top = 0; 1740 else if (rd->index->max - rd->index->current < rd->index->pagelen - rd->indicator) 1741 rd->index->top = rd->index->max - rd->index->pagelen; 1742 else 1743 rd->index->top = rd->index->current - rd->indicator; 1744 1745 menu_redraw_index(rd->index); 1746 } 1747 1748 pager_menu->redraw |= REDRAW_BODY | REDRAW_INDEX | REDRAW_STATUS; 1749#ifdef USE_SIDEBAR 1750 pager_menu->redraw |= REDRAW_SIDEBAR; 1751#endif 1752 mutt_show_error (); 1753 } 1754 1755 if (pager_menu->redraw & REDRAW_FLOW) 1756 { 1757 if (!(rd->flags & MUTT_PAGER_RETWINCH)) 1758 { 1759 rd->lines = -1; 1760 for (i = 0; i <= rd->topline; i++) 1761 if (!rd->lineInfo[i].continuation) 1762 rd->lines++; 1763 for (i = 0; i < rd->maxLine; i++) 1764 { 1765 rd->lineInfo[i].offset = 0; 1766 rd->lineInfo[i].type = -1; 1767 rd->lineInfo[i].continuation = 0; 1768 rd->lineInfo[i].chunks = 0; 1769 rd->lineInfo[i].search_cnt = -1; 1770 rd->lineInfo[i].quote = NULL; 1771 1772 safe_realloc (&(rd->lineInfo[i].syntax), 1773 sizeof (struct syntax_t)); 1774 if (rd->SearchCompiled && rd->lineInfo[i].search) 1775 FREE (&(rd->lineInfo[i].search)); 1776 } 1777 1778 rd->lastLine = 0; 1779 rd->topline = 0; 1780 } 1781 i = -1; 1782 j = -1; 1783 while (display_line (rd->fp, &rd->last_pos, &rd->lineInfo, ++i, &rd->lastLine, &rd->maxLine, 1784 rd->has_types | rd->SearchFlag | (rd->flags & MUTT_PAGER_NOWRAP), 1785 &rd->QuoteList, &rd->q_level, &rd->force_redraw, 1786 &rd->SearchRE, rd->pager_window) == 0) 1787 if (!rd->lineInfo[i].continuation && ++j == rd->lines) 1788 { 1789 rd->topline = i; 1790 if (!rd->SearchFlag) 1791 break; 1792 } 1793 } 1794 1795#ifdef USE_SIDEBAR 1796 if (pager_menu->redraw & REDRAW_SIDEBAR) 1797 { 1798 menu_redraw_sidebar (pager_menu); 1799 } 1800#endif 1801 1802 if ((pager_menu->redraw & REDRAW_BODY) || rd->topline != rd->oldtopline) 1803 { 1804 do 1805 { 1806 mutt_window_move (rd->pager_window, 0, 0); 1807 rd->curline = rd->oldtopline = rd->topline; 1808 rd->lines = 0; 1809 rd->force_redraw = 0; 1810 1811 while (rd->lines < rd->pager_window->rows && 1812 rd->lineInfo[rd->curline].offset <= rd->sb.st_size - 1) 1813 { 1814 if (display_line (rd->fp, &rd->last_pos, &rd->lineInfo, rd->curline, &rd->lastLine, 1815 &rd->maxLine, 1816 (rd->flags & MUTT_DISPLAYFLAGS) | rd->hideQuoted | rd->SearchFlag | (rd->flags & MUTT_PAGER_NOWRAP), 1817 &rd->QuoteList, &rd->q_level, &rd->force_redraw, &rd->SearchRE, 1818 rd->pager_window) > 0) 1819 rd->lines++; 1820 rd->curline++; 1821 mutt_window_move (rd->pager_window, rd->lines, 0); 1822 } 1823 rd->last_offset = rd->lineInfo[rd->curline].offset; 1824 } while (rd->force_redraw); 1825 1826 SETCOLOR (MT_COLOR_TILDE); 1827 while (rd->lines < rd->pager_window->rows) 1828 { 1829 mutt_window_clrtoeol (rd->pager_window); 1830 if (option (OPTTILDE)) 1831 addch ('~'); 1832 rd->lines++; 1833 mutt_window_move (rd->pager_window, rd->lines, 0); 1834 } 1835 NORMAL_COLOR; 1836 1837 /* We are going to update the pager status bar, so it isn't 1838 * necessary to reset to normal color now. */ 1839 1840 pager_menu->redraw |= REDRAW_STATUS; /* need to update the % seen */ 1841 } 1842 1843 if (pager_menu->redraw & REDRAW_STATUS) 1844 { 1845 struct hdr_format_info hfi; 1846 char pager_progress_str[4]; 1847 1848 hfi.ctx = Context; 1849 hfi.pager_progress = pager_progress_str; 1850 1851 if (rd->last_pos < rd->sb.st_size - 1) 1852 snprintf(pager_progress_str, sizeof(pager_progress_str), OFF_T_FMT "%%", (100 * rd->last_offset / rd->sb.st_size)); 1853 else 1854 strfcpy(pager_progress_str, (rd->topline == 0) ? "all" : "end", sizeof(pager_progress_str)); 1855 1856 /* print out the pager status bar */ 1857 mutt_window_move (rd->pager_status_window, 0, 0); 1858 SETCOLOR (MT_COLOR_STATUS); 1859 1860 if (IsHeader (rd->extra) || IsMsgAttach (rd->extra)) 1861 { 1862 size_t l1 = rd->pager_status_window->cols * MB_LEN_MAX; 1863 size_t l2 = sizeof (buffer); 1864 hfi.hdr = (IsHeader (rd->extra)) ? rd->extra->hdr : rd->extra->bdy->hdr; 1865 mutt_make_string_info (buffer, l1 < l2 ? l1 : l2, rd->pager_status_window->cols, NONULL (PagerFmt), &hfi, 0); 1866 mutt_paddstr (rd->pager_status_window->cols, buffer); 1867 } 1868 else 1869 { 1870 char bn[STRING]; 1871 snprintf (bn, sizeof (bn), "%s (%s)", rd->banner, pager_progress_str); 1872 mutt_paddstr (rd->pager_status_window->cols, bn); 1873 } 1874 NORMAL_COLOR; 1875 if (option(OPTTSENABLED) && TSSupported) 1876 { 1877 menu_status_line (buffer, sizeof (buffer), rd->index, NONULL (TSStatusFormat)); 1878 mutt_ts_status(buffer); 1879 menu_status_line (buffer, sizeof (buffer), rd->index, NONULL (TSIconFormat)); 1880 mutt_ts_icon(buffer); 1881 } 1882 } 1883 1884 if ((pager_menu->redraw & REDRAW_INDEX) && rd->index) 1885 { 1886 /* redraw the pager_index indicator, because the 1887 * flags for this message might have changed. */ 1888 if (rd->index_window->rows > 0) 1889 menu_redraw_current (rd->index); 1890 1891 /* print out the index status bar */ 1892 menu_status_line (buffer, sizeof (buffer), rd->index, NONULL(Status)); 1893 1894 mutt_window_move (rd->index_status_window, 0, 0); 1895 SETCOLOR (MT_COLOR_STATUS); 1896 mutt_paddstr (rd->index_status_window->cols, buffer); 1897 NORMAL_COLOR; 1898 } 1899 1900 pager_menu->redraw = 0; 1901} 1902 1903/* This pager is actually not so simple as it once was. It now operates in 1904 two modes: one for viewing messages and the other for viewing help. These 1905 can be distinguished by whether or not ``hdr'' is NULL. The ``hdr'' arg 1906 is there so that we can do operations on the current message without the 1907 need to pop back out to the main-menu. */ 1908int 1909mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) 1910{ 1911 static char searchbuf[STRING] = ""; 1912 char buffer[LONG_STRING]; 1913 BUFFER *helpstr = NULL; 1914 int i, ch = 0, rc = -1; 1915 int err, first = 1; 1916 int r = -1, wrapped = 0, searchctx = 0; 1917 1918 MUTTMENU *pager_menu = NULL; 1919 int old_PagerIndexLines; /* some people want to resize it 1920 * while inside the pager... */ 1921 1922 pager_redraw_data_t rd; 1923 1924 if (!(flags & MUTT_SHOWCOLOR)) 1925 flags |= MUTT_SHOWFLAT; 1926 1927 memset (&rd, 0, sizeof (rd)); 1928 rd.banner = banner; 1929 rd.flags = flags; 1930 rd.extra = extra; 1931 rd.indexlen = PagerIndexLines; 1932 rd.indicator = rd.indexlen / 3; 1933 rd.searchbuf = searchbuf; 1934 rd.has_types = (IsHeader(extra) || (flags & MUTT_SHOWCOLOR)) ? MUTT_TYPES : 0; /* main message or rfc822 attachment */ 1935 1936 if ((rd.fp = fopen (fname, "r")) == NULL) 1937 { 1938 mutt_perror (fname); 1939 return (-1); 1940 } 1941 1942 if (stat (fname, &rd.sb) != 0) 1943 { 1944 mutt_perror (fname); 1945 safe_fclose (&rd.fp); 1946 return (-1); 1947 } 1948 unlink (fname); 1949 1950 /* Initialize variables */ 1951 1952 if (IsHeader (extra) && !extra->hdr->read) 1953 { 1954 Context->msgnotreadyet = extra->hdr->msgno; 1955 mutt_set_flag (Context, extra->hdr, MUTT_READ, 1); 1956 } 1957 1958 rd.lineInfo = safe_malloc (sizeof (struct line_t) * (rd.maxLine = LINES)); 1959 for (i = 0 ; i < rd.maxLine ; i++) 1960 { 1961 memset (&rd.lineInfo[i], 0, sizeof (struct line_t)); 1962 rd.lineInfo[i].type = -1; 1963 rd.lineInfo[i].search_cnt = -1; 1964 rd.lineInfo[i].syntax = safe_malloc (sizeof (struct syntax_t)); 1965 (rd.lineInfo[i].syntax)[0].first = (rd.lineInfo[i].syntax)[0].last = -1; 1966 } 1967 1968 helpstr = mutt_buffer_new (); 1969 mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelp); 1970 mutt_buffer_strcpy (helpstr, buffer); 1971 if (IsHeader (extra)) 1972 { 1973 mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra); 1974 mutt_buffer_addch (helpstr, ' '); 1975 mutt_buffer_addstr (helpstr, buffer); 1976 } 1977 if (!InHelp) 1978 { 1979 mutt_make_help (buffer, sizeof (buffer), _("Help"), MENU_PAGER, OP_HELP); 1980 mutt_buffer_addch (helpstr, ' '); 1981 mutt_buffer_addstr (helpstr, buffer); 1982 } 1983 rd.helpstr = mutt_b2s (helpstr); 1984 1985 rd.index_status_window = safe_calloc (sizeof (mutt_window_t), 1); 1986 rd.index_window = safe_calloc (sizeof (mutt_window_t), 1); 1987 rd.pager_status_window = safe_calloc (sizeof (mutt_window_t), 1); 1988 rd.pager_window = safe_calloc (sizeof (mutt_window_t), 1); 1989 1990 pager_menu = mutt_new_menu (MENU_PAGER); 1991 pager_menu->custom_menu_redraw = pager_menu_redraw; 1992 pager_menu->redraw_data = &rd; 1993 mutt_push_current_menu (pager_menu); 1994 1995 while (ch != -1) 1996 { 1997 mutt_curs_set (0); 1998 1999 pager_menu_redraw (pager_menu); 2000 2001 if (option(OPTBRAILLEFRIENDLY)) 2002 { 2003 if (brailleLine!=-1) 2004 { 2005 move(brailleLine+1, 0); 2006 brailleLine = -1; 2007 } 2008 } 2009 else 2010 mutt_window_move (rd.pager_status_window, 0, rd.pager_status_window->cols-1); 2011 2012 mutt_refresh (); 2013 2014 if (IsHeader (extra) && OldHdr == extra->hdr && TopLine != rd.topline 2015 && rd.lineInfo[rd.curline].offset < rd.sb.st_size-1) 2016 { 2017 if (TopLine - rd.topline > rd.lines) 2018 rd.topline += rd.lines; 2019 else 2020 rd.topline = TopLine; 2021 continue; 2022 } 2023 else 2024 OldHdr = NULL; 2025 2026 ch = km_dokey (MENU_PAGER); 2027 if (ch >= 0) 2028 mutt_clear_error (); 2029 mutt_curs_set (1); 2030 2031#if defined (USE_SLANG_CURSES) || defined (HAVE_RESIZETERM) 2032 if (SigWinch) 2033 { 2034 SigWinch = 0; 2035 mutt_resize_screen (); 2036 clearok(stdscr,TRUE);/*force complete redraw*/ 2037 2038 if (flags & MUTT_PAGER_RETWINCH) 2039 { 2040 /* Store current position. */ 2041 rd.lines = -1; 2042 for (i = 0; i <= rd.topline; i++) 2043 if (!rd.lineInfo[i].continuation) 2044 rd.lines++; 2045 2046 Resize = safe_malloc (sizeof (struct resize)); 2047 2048 Resize->line = rd.lines; 2049 Resize->SearchCompiled = rd.SearchCompiled; 2050 Resize->SearchBack = rd.SearchBack; 2051 2052 ch = -1; 2053 rc = OP_REFORMAT_WINCH; 2054 } 2055 else 2056 { 2057 /* note: mutt_resize_screen() -> mutt_reflow_windows() sets 2058 * REDRAW_FULL and REDRAW_FLOW */ 2059 ch = 0; 2060 } 2061 continue; 2062 } 2063#endif 2064 if (ch < 0) 2065 { 2066 ch = 0; 2067 continue; 2068 } 2069 2070 rc = ch; 2071 2072 switch (ch) 2073 { 2074 case OP_EXIT: 2075 rc = -1; 2076 ch = -1; 2077 break; 2078 2079 case OP_QUIT: 2080 if (query_quadoption (OPT_QUIT, _("Quit Mutt?")) == MUTT_YES) 2081 { 2082 /* avoid prompting again in the index menu */ 2083 set_quadoption (OPT_QUIT, MUTT_YES); 2084 ch = -1; 2085 } 2086 break; 2087 2088 case OP_NEXT_PAGE: 2089 if (rd.lineInfo[rd.curline].offset < rd.sb.st_size-1) 2090 { 2091 rd.topline = upNLines (PagerContext, rd.lineInfo, rd.curline, rd.hideQuoted); 2092 } 2093 else if (option (OPTPAGERSTOP)) 2094 { 2095 /* emulate "less -q" and don't go on to the next message. */ 2096 mutt_error _("Bottom of message is shown."); 2097 } 2098 else 2099 { 2100 /* end of the current message, so display the next message. */ 2101 rc = OP_MAIN_NEXT_UNDELETED; 2102 ch = -1; 2103 } 2104 break; 2105 2106 case OP_PREV_PAGE: 2107 if (rd.topline != 0) 2108 { 2109 rd.topline = upNLines (rd.pager_window->rows-PagerContext, rd.lineInfo, rd.topline, rd.hideQuoted); 2110 } 2111 else 2112 mutt_error _("Top of message is shown."); 2113 break; 2114 2115 case OP_NEXT_LINE: 2116 if (rd.lineInfo[rd.curline].offset < rd.sb.st_size-1) 2117 { 2118 rd.topline++; 2119 if (rd.hideQuoted) 2120 { 2121 while (rd.lineInfo[rd.topline].type == MT_COLOR_QUOTED && 2122 rd.topline < rd.lastLine) 2123 rd.topline++; 2124 } 2125 } 2126 else 2127 mutt_error _("Bottom of message is shown."); 2128 break; 2129 2130 case OP_PREV_LINE: 2131 if (rd.topline) 2132 rd.topline = upNLines (1, rd.lineInfo, rd.topline, rd.hideQuoted); 2133 else 2134 mutt_error _("Top of message is shown."); 2135 break; 2136 2137 case OP_PAGER_TOP: 2138 if (rd.topline) 2139 rd.topline = 0; 2140 else 2141 mutt_error _("Top of message is shown."); 2142 break; 2143 2144 case OP_HALF_UP: 2145 if (rd.topline) 2146 rd.topline = upNLines (rd.pager_window->rows/2 + rd.pager_window->rows%2, 2147 rd.lineInfo, rd.topline, rd.hideQuoted); 2148 else 2149 mutt_error _("Top of message is shown."); 2150 break; 2151 2152 case OP_HALF_DOWN: 2153 if (rd.lineInfo[rd.curline].offset < rd.sb.st_size-1) 2154 { 2155 rd.topline = upNLines (rd.pager_window->rows/2, rd.lineInfo, rd.curline, rd.hideQuoted); 2156 } 2157 else if (option (OPTPAGERSTOP)) 2158 { 2159 /* emulate "less -q" and don't go on to the next message. */ 2160 mutt_error _("Bottom of message is shown."); 2161 } 2162 else 2163 { 2164 /* end of the current message, so display the next message. */ 2165 rc = OP_MAIN_NEXT_UNDELETED; 2166 ch = -1; 2167 } 2168 break; 2169 2170 case OP_SEARCH_NEXT: 2171 case OP_SEARCH_OPPOSITE: 2172 if (rd.SearchCompiled) 2173 { 2174 wrapped = 0; 2175 2176 if (SearchContext > 0 && SearchContext < rd.pager_window->rows) 2177 searchctx = SearchContext; 2178 else 2179 searchctx = 0; 2180 2181search_next: 2182 if ((!rd.SearchBack && ch==OP_SEARCH_NEXT) || 2183 (rd.SearchBack &&ch==OP_SEARCH_OPPOSITE)) 2184 { 2185 /* searching forward */ 2186 for (i = wrapped ? 0 : rd.topline + searchctx + 1; i < rd.lastLine; i++) 2187 { 2188 if ((!rd.hideQuoted || rd.lineInfo[i].type != MT_COLOR_QUOTED) && 2189 !rd.lineInfo[i].continuation && rd.lineInfo[i].search_cnt > 0) 2190 break; 2191 } 2192 2193 if (i < rd.lastLine) 2194 rd.topline = i; 2195 else if (wrapped || !option (OPTWRAPSEARCH)) 2196 mutt_error _("Not found."); 2197 else 2198 { 2199 mutt_message _("Search wrapped to top."); 2200 wrapped = 1; 2201 goto search_next; 2202 } 2203 } 2204 else 2205 { 2206 /* searching backward */ 2207 for (i = wrapped ? rd.lastLine : rd.topline + searchctx - 1; i >= 0; i--) 2208 { 2209 if ((!rd.hideQuoted || (rd.has_types && 2210 rd.lineInfo[i].type != MT_COLOR_QUOTED)) && 2211 !rd.lineInfo[i].continuation && rd.lineInfo[i].search_cnt > 0) 2212 break; 2213 } 2214 2215 if (i >= 0) 2216 rd.topline = i; 2217 else if (wrapped || !option (OPTWRAPSEARCH)) 2218 mutt_error _("Not found."); 2219 else 2220 { 2221 mutt_message _("Search wrapped to bottom."); 2222 wrapped = 1; 2223 goto search_next; 2224 } 2225 } 2226 2227 if (rd.lineInfo[rd.topline].search_cnt > 0) 2228 { 2229 rd.SearchFlag = MUTT_SEARCH; 2230 /* give some context for search results */ 2231 if (rd.topline - searchctx > 0) 2232 rd.topline -= searchctx; 2233 } 2234 2235 break; 2236 } 2237 /* fall through */ 2238 /* no previous search pattern, so fall through to search */ 2239 2240 case OP_SEARCH: 2241 case OP_SEARCH_REVERSE: 2242 strfcpy (buffer, searchbuf, sizeof (buffer)); 2243 if (mutt_get_field ((ch == OP_SEARCH || ch == OP_SEARCH_NEXT) ? 2244 _("Search for: ") : _("Reverse search for: "), 2245 buffer, sizeof (buffer), 2246 MUTT_CLEAR) != 0) 2247 break; 2248 2249 if (!strcmp (buffer, searchbuf)) 2250 { 2251 if (rd.SearchCompiled) 2252 { 2253 /* do an implicit search-next */ 2254 if (ch == OP_SEARCH) 2255 ch = OP_SEARCH_NEXT; 2256 else 2257 ch = OP_SEARCH_OPPOSITE; 2258 2259 wrapped = 0; 2260 goto search_next; 2261 } 2262 } 2263 2264 if (!buffer[0]) 2265 break; 2266 2267 strfcpy (searchbuf, buffer, sizeof (searchbuf)); 2268 2269 /* leave SearchBack alone if ch == OP_SEARCH_NEXT */ 2270 if (ch == OP_SEARCH) 2271 rd.SearchBack = 0; 2272 else if (ch == OP_SEARCH_REVERSE) 2273 rd.SearchBack = 1; 2274 2275 if (rd.SearchCompiled) 2276 { 2277 regfree (&rd.SearchRE); 2278 for (i = 0; i < rd.lastLine; i++) 2279 { 2280 if (rd.lineInfo[i].search) 2281 FREE (&(rd.lineInfo[i].search)); 2282 rd.lineInfo[i].search_cnt = -1; 2283 } 2284 } 2285 2286 if ((err = REGCOMP (&rd.SearchRE, searchbuf, REG_NEWLINE | mutt_which_case (searchbuf))) != 0) 2287 { 2288 regerror (err, &rd.SearchRE, buffer, sizeof (buffer)); 2289 mutt_error ("%s", buffer); 2290 for (i = 0; i < rd.maxLine ; i++) 2291 { 2292 /* cleanup */ 2293 if (rd.lineInfo[i].search) 2294 FREE (&(rd.lineInfo[i].search)); 2295 rd.lineInfo[i].search_cnt = -1; 2296 } 2297 rd.SearchFlag = 0; 2298 rd.SearchCompiled = 0; 2299 } 2300 else 2301 { 2302 rd.SearchCompiled = 1; 2303 /* update the search pointers */ 2304 i = 0; 2305 while (display_line (rd.fp, &rd.last_pos, &rd.lineInfo, i, &rd.lastLine, 2306 &rd.maxLine, MUTT_SEARCH | (flags & MUTT_PAGER_NSKIP) | (flags & MUTT_PAGER_NOWRAP), 2307 &rd.QuoteList, &rd.q_level, 2308 &rd.force_redraw, &rd.SearchRE, rd.pager_window) == 0) 2309 i++; 2310 2311 if (!rd.SearchBack) 2312 { 2313 /* searching forward */ 2314 for (i = rd.topline; i < rd.lastLine; i++) 2315 { 2316 if ((!rd.hideQuoted || rd.lineInfo[i].type != MT_COLOR_QUOTED) && 2317 !rd.lineInfo[i].continuation && rd.lineInfo[i].search_cnt > 0) 2318 break; 2319 } 2320 2321 if (i < rd.lastLine) rd.topline = i; 2322 } 2323 else 2324 { 2325 /* searching backward */ 2326 for (i = rd.topline; i >= 0; i--) 2327 { 2328 if ((!rd.hideQuoted || rd.lineInfo[i].type != MT_COLOR_QUOTED) && 2329 !rd.lineInfo[i].continuation && rd.lineInfo[i].search_cnt > 0) 2330 break; 2331 } 2332 2333 if (i >= 0) rd.topline = i; 2334 } 2335 2336 if (rd.lineInfo[rd.topline].search_cnt == 0) 2337 { 2338 rd.SearchFlag = 0; 2339 mutt_error _("Not found."); 2340 } 2341 else 2342 { 2343 rd.SearchFlag = MUTT_SEARCH; 2344 /* give some context for search results */ 2345 if (SearchContext > 0 && SearchContext < rd.pager_window->rows) 2346 searchctx = SearchContext; 2347 else 2348 searchctx = 0; 2349 if (rd.topline - searchctx > 0) 2350 rd.topline -= searchctx; 2351 } 2352 2353 } 2354 pager_menu->redraw = REDRAW_BODY; 2355 break; 2356 2357 case OP_SEARCH_TOGGLE: 2358 if (rd.SearchCompiled) 2359 { 2360 rd.SearchFlag ^= MUTT_SEARCH; 2361 pager_menu->redraw = REDRAW_BODY; 2362 } 2363 break; 2364 2365 case OP_SORT: 2366 case OP_SORT_REVERSE: 2367 CHECK_MODE(IsHeader (extra)); 2368 if (mutt_select_sort ((ch == OP_SORT_REVERSE)) == 0) 2369 { 2370 set_option (OPTNEEDRESORT); 2371 ch = -1; 2372 rc = OP_DISPLAY_MESSAGE; 2373 } 2374 break; 2375 2376 case OP_HELP: 2377 /* don't let the user enter the help-menu from the help screen! */ 2378 if (! InHelp) 2379 { 2380 InHelp = 1; 2381 mutt_help (MENU_PAGER); 2382 pager_menu->redraw = REDRAW_FULL; 2383 InHelp = 0; 2384 } 2385 else 2386 mutt_error _("Help is currently being shown."); 2387 break; 2388 2389 case OP_ERROR_HISTORY: 2390 mutt_error_history_display (); 2391 pager_menu->redraw = REDRAW_FULL; 2392 break; 2393 2394 case OP_PAGER_HIDE_QUOTED: 2395 if (rd.has_types) 2396 { 2397 rd.hideQuoted ^= MUTT_HIDE; 2398 if (rd.hideQuoted && rd.lineInfo[rd.topline].type == MT_COLOR_QUOTED) 2399 rd.topline = upNLines (1, rd.lineInfo, rd.topline, rd.hideQuoted); 2400 else 2401 pager_menu->redraw = REDRAW_BODY; 2402 } 2403 break; 2404 2405 case OP_PAGER_SKIP_QUOTED: 2406 if (rd.has_types) 2407 { 2408 int dretval = 0; 2409 int new_topline = rd.topline; 2410 2411 while ((new_topline < rd.lastLine || 2412 (0 == (dretval = display_line (rd.fp, &rd.last_pos, &rd.lineInfo, 2413 new_topline, &rd.lastLine, &rd.maxLine, MUTT_TYPES | (flags & MUTT_PAGER_NOWRAP), 2414 &rd.QuoteList, &rd.q_level, &rd.force_redraw, &rd.SearchRE, rd.pager_window)))) 2415 && rd.lineInfo[new_topline].type != MT_COLOR_QUOTED) 2416 new_topline++; 2417 2418 if (dretval < 0) 2419 { 2420 mutt_error _("No more quoted text."); 2421 break; 2422 } 2423 2424 while ((new_topline < rd.lastLine || 2425 (0 == (dretval = display_line (rd.fp, &rd.last_pos, &rd.lineInfo, 2426 new_topline, &rd.lastLine, &rd.maxLine, MUTT_TYPES | (flags & MUTT_PAGER_NOWRAP), 2427 &rd.QuoteList, &rd.q_level, &rd.force_redraw, &rd.SearchRE, rd.pager_window)))) 2428 && rd.lineInfo[new_topline].type == MT_COLOR_QUOTED) 2429 new_topline++; 2430 2431 if (dretval < 0) 2432 { 2433 mutt_error _("No more unquoted text after quoted text."); 2434 break; 2435 } 2436 rd.topline = new_topline; 2437 } 2438 break; 2439 2440 case OP_PAGER_BOTTOM: /* move to the end of the file */ 2441 if (rd.lineInfo[rd.curline].offset < rd.sb.st_size - 1) 2442 { 2443 i = rd.curline; 2444 /* make sure the types are defined to the end of file */ 2445 while (display_line (rd.fp, &rd.last_pos, &rd.lineInfo, i, &rd.lastLine, 2446 &rd.maxLine, rd.has_types | (flags & MUTT_PAGER_NOWRAP), 2447 &rd.QuoteList, &rd.q_level, &rd.force_redraw, 2448 &rd.SearchRE, rd.pager_window) == 0) 2449 i++; 2450 rd.topline = upNLines (rd.pager_window->rows, rd.lineInfo, rd.lastLine, rd.hideQuoted); 2451 } 2452 else 2453 mutt_error _("Bottom of message is shown."); 2454 break; 2455 2456 case OP_REDRAW: 2457 clearok (stdscr, TRUE); 2458 pager_menu->redraw = REDRAW_FULL; 2459 break; 2460 2461 case OP_NULL: 2462 km_error_key (MENU_PAGER); 2463 break; 2464 2465 /* -------------------------------------------------------------------- 2466 * The following are operations on the current message rather than 2467 * adjusting the view of the message. 2468 */ 2469 2470 case OP_BOUNCE_MESSAGE: 2471 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2472 CHECK_ATTACH; 2473 if (IsMsgAttach (extra)) 2474 mutt_attach_bounce (extra->fp, extra->hdr, 2475 extra->actx, extra->bdy); 2476 else 2477 ci_bounce_message (extra->hdr); 2478 break; 2479 2480 case OP_RESEND: 2481 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2482 CHECK_ATTACH; 2483 if (IsMsgAttach (extra)) 2484 mutt_attach_resend (extra->fp, extra->hdr, 2485 extra->actx, extra->bdy); 2486 else 2487 mutt_resend_message (NULL, extra->ctx, extra->hdr); 2488 pager_menu->redraw = REDRAW_FULL; 2489 break; 2490 2491 case OP_CHECK_TRADITIONAL: 2492 CHECK_MODE (IsHeader (extra)); 2493 if (!(WithCrypto & APPLICATION_PGP)) 2494 break; 2495 if (!(extra->hdr->security & PGP_TRADITIONAL_CHECKED)) 2496 { 2497 ch = -1; 2498 rc = OP_CHECK_TRADITIONAL; 2499 } 2500 break; 2501 2502 case OP_COMPOSE_TO_SENDER: 2503 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2504 CHECK_ATTACH; 2505 if (IsMsgAttach (extra)) 2506 mutt_attach_mail_sender (extra->fp, extra->hdr, extra->actx, 2507 extra->bdy); 2508 else 2509 ci_send_message (SENDTOSENDER, NULL, NULL, extra->ctx, extra->hdr); 2510 pager_menu->redraw = REDRAW_FULL; 2511 break; 2512 2513 case OP_CREATE_ALIAS: 2514 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2515 if (IsMsgAttach (extra)) 2516 mutt_create_alias (extra->bdy->hdr->env, NULL); 2517 else 2518 mutt_create_alias (extra->hdr->env, NULL); 2519 break; 2520 2521 case OP_PURGE_MESSAGE: 2522 case OP_DELETE: 2523 CHECK_MODE(IsHeader (extra)); 2524 CHECK_READONLY; 2525 /* L10N: CHECK_ACL */ 2526 CHECK_ACL(MUTT_ACL_DELETE, _("Cannot delete message")); 2527 2528 mutt_set_flag (Context, extra->hdr, MUTT_DELETE, 1); 2529 mutt_set_flag (Context, extra->hdr, MUTT_PURGE, (ch == OP_PURGE_MESSAGE)); 2530 if (option (OPTDELETEUNTAG)) 2531 mutt_set_flag (Context, extra->hdr, MUTT_TAG, 0); 2532 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2533 if (option (OPTRESOLVE)) 2534 { 2535 ch = -1; 2536 rc = OP_MAIN_NEXT_UNDELETED; 2537 } 2538 break; 2539 2540 case OP_MAIN_SET_FLAG: 2541 case OP_MAIN_CLEAR_FLAG: 2542 CHECK_MODE(IsHeader (extra)); 2543 CHECK_READONLY; 2544 2545 if (mutt_change_flag (extra->hdr, (ch == OP_MAIN_SET_FLAG)) == 0) 2546 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2547 if (extra->hdr->deleted && option (OPTRESOLVE)) 2548 { 2549 ch = -1; 2550 rc = OP_MAIN_NEXT_UNDELETED; 2551 } 2552 break; 2553 2554 case OP_DELETE_THREAD: 2555 case OP_DELETE_SUBTHREAD: 2556 CHECK_MODE(IsHeader (extra)); 2557 CHECK_READONLY; 2558 /* L10N: CHECK_ACL */ 2559 CHECK_ACL(MUTT_ACL_DELETE, _("Cannot delete message(s)")); 2560 2561 r = mutt_thread_set_flag (extra->hdr, MUTT_DELETE, 1, 2562 ch == OP_DELETE_THREAD ? 0 : 1); 2563 2564 if (r != -1) 2565 { 2566 if (option (OPTDELETEUNTAG)) 2567 mutt_thread_set_flag (extra->hdr, MUTT_TAG, 0, 2568 ch == OP_DELETE_THREAD ? 0 : 1); 2569 if (option (OPTRESOLVE)) 2570 { 2571 rc = OP_MAIN_NEXT_UNDELETED; 2572 ch = -1; 2573 } 2574 2575 if (!option (OPTRESOLVE) && PagerIndexLines) 2576 pager_menu->redraw = REDRAW_FULL; 2577 else 2578 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2579 } 2580 break; 2581 2582 case OP_DISPLAY_ADDRESS: 2583 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2584 if (IsMsgAttach (extra)) 2585 mutt_display_address (extra->bdy->hdr->env); 2586 else 2587 mutt_display_address (extra->hdr->env); 2588 break; 2589 2590 case OP_ENTER_COMMAND: 2591 old_PagerIndexLines = PagerIndexLines; 2592 2593 mutt_enter_command (); 2594 2595 if (option (OPTNEEDRESORT)) 2596 { 2597 unset_option (OPTNEEDRESORT); 2598 CHECK_MODE(IsHeader (extra)); 2599 set_option (OPTNEEDRESORT); 2600 } 2601 2602 if (old_PagerIndexLines != PagerIndexLines) 2603 { 2604 if (rd.index) 2605 mutt_menuDestroy (&rd.index); 2606 rd.index = NULL; 2607 } 2608 2609 if ((pager_menu->redraw & REDRAW_FLOW) && 2610 (flags & MUTT_PAGER_RETWINCH)) 2611 { 2612 ch = -1; 2613 rc = OP_REFORMAT_WINCH; 2614 continue; 2615 } 2616 2617 ch = 0; 2618 break; 2619 2620 case OP_FLAG_MESSAGE: 2621 CHECK_MODE(IsHeader (extra)); 2622 CHECK_READONLY; 2623 /* L10N: CHECK_ACL */ 2624 CHECK_ACL(MUTT_ACL_WRITE, "Cannot flag message"); 2625 2626 mutt_set_flag (Context, extra->hdr, MUTT_FLAG, !extra->hdr->flagged); 2627 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2628 if (option (OPTRESOLVE)) 2629 { 2630 ch = -1; 2631 rc = OP_MAIN_NEXT_UNDELETED; 2632 } 2633 break; 2634 2635 case OP_PIPE: 2636 CHECK_MODE(IsHeader (extra) || IsAttach (extra)); 2637 if (IsAttach (extra)) 2638 mutt_pipe_attachment_list (extra->actx, extra->fp, 0, extra->bdy, 0); 2639 else 2640 mutt_pipe_message (extra->hdr); 2641 break; 2642 2643 case OP_PRINT: 2644 CHECK_MODE(IsHeader (extra) || IsAttach (extra)); 2645 if (IsAttach (extra)) 2646 mutt_print_attachment_list (extra->actx, extra->fp, 0, extra->bdy); 2647 else 2648 mutt_print_message (extra->hdr); 2649 break; 2650 2651 case OP_MAIL: 2652 CHECK_MODE(IsHeader (extra) && !IsAttach (extra)); 2653 CHECK_ATTACH; 2654 ci_send_message (0, NULL, NULL, extra->ctx, NULL); 2655 pager_menu->redraw = REDRAW_FULL; 2656 break; 2657 2658 case OP_REPLY: 2659 case OP_GROUP_REPLY: 2660 case OP_GROUP_CHAT_REPLY: 2661 case OP_LIST_REPLY: 2662 { 2663 int replyflags; 2664 2665 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2666 CHECK_ATTACH; 2667 2668 replyflags = SENDREPLY | 2669 (ch == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) | 2670 (ch == OP_GROUP_CHAT_REPLY ? SENDGROUPCHATREPLY : 0) | 2671 (ch == OP_LIST_REPLY ? SENDLISTREPLY : 0); 2672 2673 if (IsMsgAttach (extra)) 2674 mutt_attach_reply (extra->fp, extra->hdr, extra->actx, 2675 extra->bdy, replyflags); 2676 else 2677 ci_send_message (replyflags, NULL, NULL, extra->ctx, extra->hdr); 2678 pager_menu->redraw = REDRAW_FULL; 2679 break; 2680 } 2681 2682 case OP_RECALL_MESSAGE: 2683 CHECK_MODE(IsHeader (extra) && !IsAttach(extra)); 2684 CHECK_ATTACH; 2685 ci_send_message (SENDPOSTPONED, NULL, NULL, extra->ctx, extra->hdr); 2686 pager_menu->redraw = REDRAW_FULL; 2687 break; 2688 2689 case OP_FORWARD_MESSAGE: 2690 CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); 2691 CHECK_ATTACH; 2692 if (IsMsgAttach (extra)) 2693 mutt_attach_forward (extra->fp, extra->hdr, extra->actx, 2694 extra->bdy); 2695 else 2696 ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); 2697 pager_menu->redraw = REDRAW_FULL; 2698 break; 2699 2700 case OP_DECRYPT_SAVE: 2701 if (!WithCrypto) 2702 { 2703 ch = -1; 2704 break; 2705 } 2706 /* fall through */ 2707 case OP_SAVE: 2708 if (IsAttach (extra)) 2709 { 2710 mutt_save_attachment_list (extra->actx, extra->fp, 0, extra->bdy, extra->hdr, NULL); 2711 break; 2712 } 2713 /* fall through */ 2714 case OP_COPY_MESSAGE: 2715 case OP_DECODE_SAVE: 2716 case OP_DECODE_COPY: 2717 case OP_DECRYPT_COPY: 2718 if (!WithCrypto && ch == OP_DECRYPT_COPY) 2719 { 2720 ch = -1; 2721 break; 2722 } 2723 CHECK_MODE(IsHeader (extra)); 2724 if (mutt_save_message (extra->hdr, 2725 (ch == OP_DECRYPT_SAVE) || 2726 (ch == OP_SAVE) || (ch == OP_DECODE_SAVE), 2727 (ch == OP_DECODE_SAVE) || (ch == OP_DECODE_COPY), 2728 (ch == OP_DECRYPT_SAVE) || (ch == OP_DECRYPT_COPY) || 2729 0) == 0 && 2730 (ch == OP_SAVE || ch == OP_DECODE_SAVE || ch == OP_DECRYPT_SAVE) 2731 ) 2732 { 2733 if (option (OPTRESOLVE)) 2734 { 2735 ch = -1; 2736 rc = OP_MAIN_NEXT_UNDELETED; 2737 } 2738 else 2739 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2740 } 2741 break; 2742 2743 case OP_SHELL_ESCAPE: 2744 mutt_shell_escape (); 2745 break; 2746 2747 case OP_TAG: 2748 CHECK_MODE(IsHeader (extra)); 2749 mutt_set_flag (Context, extra->hdr, MUTT_TAG, !extra->hdr->tagged); 2750 2751 Context->last_tag = extra->hdr->tagged ? extra->hdr : 2752 ((Context->last_tag == extra->hdr && !extra->hdr->tagged) 2753 ? NULL : Context->last_tag); 2754 2755 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2756 if (option (OPTRESOLVE)) 2757 { 2758 ch = -1; 2759 rc = OP_NEXT_ENTRY; 2760 } 2761 break; 2762 2763 case OP_TOGGLE_NEW: 2764 CHECK_MODE(IsHeader (extra)); 2765 CHECK_READONLY; 2766 /* L10N: CHECK_ACL */ 2767 CHECK_ACL(MUTT_ACL_SEEN, _("Cannot toggle new")); 2768 2769 if (extra->hdr->read || extra->hdr->old) 2770 mutt_set_flag (Context, extra->hdr, MUTT_NEW, 1); 2771 else if (!first) 2772 mutt_set_flag (Context, extra->hdr, MUTT_READ, 1); 2773 first = 0; 2774 Context->msgnotreadyet = -1; 2775 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2776 if (option (OPTRESOLVE)) 2777 { 2778 ch = -1; 2779 rc = OP_MAIN_NEXT_UNDELETED; 2780 } 2781 break; 2782 2783 case OP_UNDELETE: 2784 CHECK_MODE(IsHeader (extra)); 2785 CHECK_READONLY; 2786 /* L10N: CHECK_ACL */ 2787 CHECK_ACL(MUTT_ACL_DELETE, _("Cannot undelete message")); 2788 2789 mutt_set_flag (Context, extra->hdr, MUTT_DELETE, 0); 2790 mutt_set_flag (Context, extra->hdr, MUTT_PURGE, 0); 2791 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2792 if (option (OPTRESOLVE)) 2793 { 2794 ch = -1; 2795 rc = OP_NEXT_ENTRY; 2796 } 2797 break; 2798 2799 case OP_UNDELETE_THREAD: 2800 case OP_UNDELETE_SUBTHREAD: 2801 CHECK_MODE(IsHeader (extra)); 2802 CHECK_READONLY; 2803 /* L10N: CHECK_ACL */ 2804 CHECK_ACL(MUTT_ACL_DELETE, _("Cannot undelete message(s)")); 2805 2806 r = mutt_thread_set_flag (extra->hdr, MUTT_DELETE, 0, 2807 ch == OP_UNDELETE_THREAD ? 0 : 1); 2808 if (r != -1) 2809 r = mutt_thread_set_flag (extra->hdr, MUTT_PURGE, 0, 2810 ch == OP_UNDELETE_THREAD ? 0 : 1); 2811 if (r != -1) 2812 { 2813 if (option (OPTRESOLVE)) 2814 { 2815 rc = (ch == OP_DELETE_THREAD) ? 2816 OP_MAIN_NEXT_THREAD : OP_MAIN_NEXT_SUBTHREAD; 2817 ch = -1; 2818 } 2819 2820 if (!option (OPTRESOLVE) && PagerIndexLines) 2821 pager_menu->redraw = REDRAW_FULL; 2822 else 2823 pager_menu->redraw |= REDRAW_STATUS | REDRAW_INDEX; 2824 } 2825 break; 2826 2827 case OP_VERSION: 2828 mutt_version (); 2829 break; 2830 2831 case OP_BUFFY_LIST: 2832 mutt_buffy_list (); 2833 break; 2834 2835 case OP_VIEW_ATTACHMENTS: 2836 if (flags & MUTT_PAGER_ATTACHMENT) 2837 { 2838 ch = -1; 2839 rc = OP_ATTACH_COLLAPSE; 2840 break; 2841 } 2842 CHECK_MODE(IsHeader (extra)); 2843 mutt_view_attachments (extra->hdr); 2844 if (extra->hdr->attach_del) 2845 Context->changed = 1; 2846 pager_menu->redraw = REDRAW_FULL; 2847 break; 2848 2849 case OP_EDIT_LABEL: 2850 CHECK_MODE(IsHeader (extra)); 2851 rc = mutt_label_message(extra->hdr); 2852 if (rc > 0) 2853 { 2854 Context->changed = 1; 2855 pager_menu->redraw = REDRAW_FULL; 2856 mutt_message (_("%d labels changed."), rc); 2857 } 2858 else 2859 { 2860 mutt_message _("No labels changed."); 2861 } 2862 break; 2863 2864 case OP_MAIL_KEY: 2865 if (!(WithCrypto & APPLICATION_PGP)) 2866 { 2867 ch = -1; 2868 break; 2869 } 2870 CHECK_MODE(IsHeader(extra)); 2871 CHECK_ATTACH; 2872 ci_send_message (SENDKEY, NULL, NULL, extra->ctx, extra->hdr); 2873 pager_menu->redraw = REDRAW_FULL; 2874 break; 2875 2876 2877 case OP_FORGET_PASSPHRASE: 2878 crypt_forget_passphrase (); 2879 break; 2880 2881 case OP_EXTRACT_KEYS: 2882 if (!WithCrypto) 2883 { 2884 ch = -1; 2885 break; 2886 } 2887 CHECK_MODE(IsHeader(extra)); 2888 crypt_extract_keys_from_messages(extra->hdr); 2889 pager_menu->redraw = REDRAW_FULL; 2890 break; 2891 2892 case OP_WHAT_KEY: 2893 mutt_what_key (); 2894 break; 2895 2896 case OP_CHECK_STATS: 2897 mutt_check_stats (); 2898 break; 2899 2900#ifdef USE_SIDEBAR 2901 case OP_SIDEBAR_NEXT: 2902 case OP_SIDEBAR_NEXT_NEW: 2903 case OP_SIDEBAR_PAGE_DOWN: 2904 case OP_SIDEBAR_PAGE_UP: 2905 case OP_SIDEBAR_PREV: 2906 case OP_SIDEBAR_PREV_NEW: 2907 mutt_sb_change_mailbox (ch); 2908 break; 2909 2910 case OP_SIDEBAR_TOGGLE_VISIBLE: 2911 toggle_option (OPTSIDEBAR); 2912 mutt_reflow_windows(); 2913 break; 2914#endif 2915 2916 default: 2917 ch = -1; 2918 break; 2919 } 2920 } 2921 2922 safe_fclose (&rd.fp); 2923 if (IsHeader (extra)) 2924 { 2925 Context->msgnotreadyet = -1; 2926 switch (rc) 2927 { 2928 case -1: 2929 case OP_DISPLAY_HEADERS: 2930 mutt_clear_pager_position (); 2931 break; 2932 default: 2933 TopLine = rd.topline; 2934 OldHdr = extra->hdr; 2935 break; 2936 } 2937 } 2938 2939 cleanup_quote (&rd.QuoteList); 2940 2941 for (i = 0; i < rd.maxLine ; i++) 2942 { 2943 FREE (&(rd.lineInfo[i].syntax)); 2944 if (rd.SearchCompiled && rd.lineInfo[i].search) 2945 FREE (&(rd.lineInfo[i].search)); 2946 } 2947 if (rd.SearchCompiled) 2948 { 2949 regfree (&rd.SearchRE); 2950 rd.SearchCompiled = 0; 2951 } 2952 FREE (&rd.lineInfo); 2953 mutt_pop_current_menu (pager_menu); 2954 mutt_menuDestroy (&pager_menu); 2955 if (rd.index) 2956 mutt_menuDestroy(&rd.index); 2957 2958 mutt_buffer_free (&helpstr); 2959 FREE (&rd.index_status_window); 2960 FREE (&rd.index_window); 2961 FREE (&rd.pager_status_window); 2962 FREE (&rd.pager_window); 2963 2964 return (rc != -1 ? rc : 0); 2965}