mutt stable branch with some hacks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at jcs 594 lines 14 kB view raw
1/* 2 * Copyright (C) 1996-2000 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 "history.h" 25#include "mutt_menu.h" 26 27#include <stdint.h> 28 29/* This history ring grows from 0..HistSize, with last marking the 30 * where new entries go: 31 * 0 the oldest entry in the ring 32 * 1 entry 33 * ... 34 * x-1 most recently entered text 35 * last-> x NULL (this will be overwritten next) 36 * x+1 NULL 37 * ... 38 * HistSize NULL 39 * 40 * Once the array fills up, it is used as a ring. last points where a new 41 * entry will go. Older entries are "up", and wrap around: 42 * 0 entry 43 * 1 entry 44 * ... 45 * y-1 most recently entered text 46 * last-> y entry (this will be overwritten next) 47 * y+1 the oldest entry in the ring 48 * ... 49 * HistSize entry 50 * 51 * When $history_remove_dups is set, duplicate entries are scanned and removed 52 * each time a new entry is added. In order to preserve the history ring size, 53 * entries 0..last are compacted up. Entries last+1..HistSize are 54 * compacted down: 55 * 0 entry 56 * 1 entry 57 * ... 58 * z-1 most recently entered text 59 * last-> z entry (this will be overwritten next) 60 * z+1 NULL 61 * z+2 NULL 62 * ... 63 * the oldest entry in the ring 64 * next oldest entry 65 * HistSize entry 66 */ 67struct history 68{ 69 char **hist; 70 short cur; 71 short last; 72}; 73 74static const struct mapping_t HistoryHelp[] = { 75 { N_("Exit"), OP_EXIT }, 76 { N_("Select"), OP_GENERIC_SELECT_ENTRY }, 77 { N_("Search"), OP_SEARCH }, 78 { N_("Help"), OP_HELP }, 79 { NULL, 0 } 80}; 81 82/* global vars used for the string-history routines */ 83 84static struct history History[HC_LAST]; 85static int OldSize = 0; 86 87#define GET_HISTORY(CLASS) ((CLASS >= HC_LAST) ? NULL : &History[CLASS]) 88 89static void init_history (struct history *h) 90{ 91 int i; 92 93 if (OldSize) 94 { 95 if (h->hist) 96 { 97 for (i = 0 ; i <= OldSize ; i ++) 98 FREE (&h->hist[i]); 99 FREE (&h->hist); 100 } 101 } 102 103 if (HistSize) 104 h->hist = safe_calloc (HistSize + 1, sizeof (char *)); 105 106 h->cur = 0; 107 h->last = 0; 108} 109 110void mutt_read_histfile (void) 111{ 112 FILE *f; 113 int line = 0, hclass, read; 114 char *linebuf = NULL, *p; 115 size_t buflen; 116 117 if (!HistFile) 118 return; 119 120 if ((f = fopen (HistFile, "r")) == NULL) 121 return; 122 123 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL) 124 { 125 read = 0; 126 if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 || 127 *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0) 128 { 129 mutt_error (_("Bad history file format (line %d)"), line); 130 break; 131 } 132 /* silently ignore too high class (probably newer mutt) */ 133 if (hclass >= HC_LAST) 134 continue; 135 *p = '\0'; 136 p = safe_strdup (linebuf + read); 137 if (p) 138 { 139 mutt_convert_string (&p, "utf-8", Charset, 0); 140 mutt_history_add (hclass, p, 0); 141 FREE (&p); 142 } 143 } 144 145 safe_fclose (&f); 146 FREE (&linebuf); 147} 148 149static int dup_hash_dec (HASH *dup_hash, char *s) 150{ 151 struct hash_elem *elem; 152 uintptr_t count; 153 154 elem = hash_find_elem (dup_hash, s); 155 if (!elem) 156 return -1; 157 158 count = (uintptr_t)elem->data; 159 if (count <= 1) 160 { 161 hash_delete (dup_hash, s, NULL, NULL); 162 return 0; 163 } 164 165 count--; 166 elem->data = (void *)count; 167 return count; 168} 169 170static int dup_hash_inc (HASH *dup_hash, char *s) 171{ 172 struct hash_elem *elem; 173 uintptr_t count; 174 175 elem = hash_find_elem (dup_hash, s); 176 if (!elem) 177 { 178 count = 1; 179 hash_insert (dup_hash, s, (void *)count); 180 return count; 181 } 182 183 count = (uintptr_t)elem->data; 184 count++; 185 elem->data = (void *)count; 186 return count; 187} 188 189static void shrink_histfile (void) 190{ 191 BUFFER *tmpfname = NULL; 192 FILE *f, *tmp = NULL; 193 int n[HC_LAST] = { 0 }; 194 int line, hclass, read; 195 char *linebuf = NULL, *p; 196 size_t buflen; 197 int regen_file = 0; 198 HASH *dup_hashes[HC_LAST] = { 0 }; 199 200 if ((f = fopen (HistFile, "r")) == NULL) 201 return; 202 203 if (option (OPTHISTREMOVEDUPS)) 204 for (hclass = 0; hclass < HC_LAST; hclass++) 205 dup_hashes[hclass] = hash_create (MAX (10, SaveHist * 2), MUTT_HASH_STRDUP_KEYS); 206 207 line = 0; 208 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL) 209 { 210 if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 || 211 *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0) 212 { 213 mutt_error (_("Bad history file format (line %d)"), line); 214 goto cleanup; 215 } 216 /* silently ignore too high class (probably newer mutt) */ 217 if (hclass >= HC_LAST) 218 continue; 219 *p = '\0'; 220 if (option (OPTHISTREMOVEDUPS) && 221 (dup_hash_inc (dup_hashes[hclass], linebuf + read) > 1)) 222 { 223 regen_file = 1; 224 continue; 225 } 226 n[hclass]++; 227 } 228 229 if (!regen_file) 230 for (hclass = HC_FIRST; hclass < HC_LAST; hclass++) 231 if (n[hclass] > SaveHist) 232 { 233 regen_file = 1; 234 break; 235 } 236 237 if (regen_file) 238 { 239 tmpfname = mutt_buffer_pool_get (); 240 mutt_buffer_mktemp (tmpfname); 241 if ((tmp = safe_fopen (mutt_b2s (tmpfname), "w+")) == NULL) 242 { 243 mutt_perror (mutt_b2s (tmpfname)); 244 goto cleanup; 245 } 246 rewind (f); 247 line = 0; 248 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL) 249 { 250 if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 || 251 *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0) 252 { 253 mutt_error (_("Bad history file format (line %d)"), line); 254 goto cleanup; 255 } 256 if (hclass >= HC_LAST) 257 continue; 258 *p = '\0'; 259 if (option (OPTHISTREMOVEDUPS) && 260 (dup_hash_dec (dup_hashes[hclass], linebuf + read) > 0)) 261 continue; 262 *p = '|'; 263 if (n[hclass]-- <= SaveHist) 264 fprintf (tmp, "%s\n", linebuf); 265 } 266 } 267 268cleanup: 269 safe_fclose (&f); 270 FREE (&linebuf); 271 if (tmp != NULL) 272 { 273 if (fflush (tmp) == 0 && 274 (f = fopen (HistFile, "w")) != NULL) /* __FOPEN_CHECKED__ */ 275 { 276 rewind (tmp); 277 mutt_copy_stream (tmp, f); 278 safe_fclose (&f); 279 } 280 safe_fclose (&tmp); 281 unlink (mutt_b2s (tmpfname)); 282 } 283 mutt_buffer_pool_release (&tmpfname); 284 if (option (OPTHISTREMOVEDUPS)) 285 for (hclass = 0; hclass < HC_LAST; hclass++) 286 hash_destroy (&dup_hashes[hclass], NULL); 287} 288 289static void save_history (history_class_t hclass, const char *s) 290{ 291 static int n = 0; 292 FILE *f; 293 char *tmp, *p; 294 295 if (!s || !*s) /* This shouldn't happen, but it's safer. */ 296 return; 297 298 if ((f = fopen (HistFile, "a")) == NULL) 299 { 300 mutt_perror ("fopen"); 301 return; 302 } 303 304 tmp = safe_strdup (s); 305 mutt_convert_string (&tmp, Charset, "utf-8", 0); 306 307 /* Format of a history item (1 line): "<histclass>:<string>|". 308 We add a '|' in order to avoid lines ending with '\'. */ 309 fprintf (f, "%d:", (int) hclass); 310 for (p = tmp; *p; p++) 311 { 312 /* Don't copy \n as a history item must fit on one line. The string 313 shouldn't contain such a character anyway, but as this can happen 314 in practice, we must deal with that. */ 315 if (*p != '\n') 316 putc ((unsigned char) *p, f); 317 } 318 fputs ("|\n", f); 319 320 safe_fclose (&f); 321 FREE (&tmp); 322 323 if (--n < 0) 324 { 325 n = SaveHist; 326 shrink_histfile(); 327 } 328} 329 330/* When removing dups, we want the created "blanks" to be right below the 331 * resulting h->last position. See the comment section above 'struct history'. 332 */ 333static void remove_history_dups (history_class_t hclass, const char *s) 334{ 335 int source, dest, old_last; 336 struct history *h = GET_HISTORY(hclass); 337 338 if (!HistSize || !h) 339 return; /* disabled */ 340 341 /* Remove dups from 0..last-1 compacting up. */ 342 source = dest = 0; 343 while (source < h->last) 344 { 345 if (!mutt_strcmp (h->hist[source], s)) 346 FREE (&h->hist[source++]); 347 else 348 h->hist[dest++] = h->hist[source++]; 349 } 350 351 /* Move 'last' entry up. */ 352 h->hist[dest] = h->hist[source]; 353 old_last = h->last; 354 h->last = dest; 355 356 /* Fill in moved entries with NULL */ 357 while (source > h->last) 358 h->hist[source--] = NULL; 359 360 /* Remove dups from last+1 .. HistSize compacting down. */ 361 source = dest = HistSize; 362 while (source > old_last) 363 { 364 if (!mutt_strcmp (h->hist[source], s)) 365 FREE (&h->hist[source--]); 366 else 367 h->hist[dest--] = h->hist[source--]; 368 } 369 370 /* Fill in moved entries with NULL */ 371 while (dest > old_last) 372 h->hist[dest--] = NULL; 373} 374 375void mutt_init_history(void) 376{ 377 history_class_t hclass; 378 379 if (HistSize == OldSize) 380 return; 381 382 for (hclass = HC_FIRST; hclass < HC_LAST; hclass++) 383 init_history(&History[hclass]); 384 385 OldSize = HistSize; 386} 387 388void mutt_history_add (history_class_t hclass, const char *s, int save) 389{ 390 int prev; 391 struct history *h = GET_HISTORY(hclass); 392 393 if (!HistSize || !h) 394 return; /* disabled */ 395 396 if (*s) 397 { 398 prev = h->last - 1; 399 if (prev < 0) prev = HistSize; 400 401 /* don't add to prompt history: 402 * - lines beginning by a space 403 * - repeated lines 404 */ 405 if (*s != ' ' && (!h->hist[prev] || mutt_strcmp (h->hist[prev], s) != 0)) 406 { 407 if (option (OPTHISTREMOVEDUPS)) 408 remove_history_dups (hclass, s); 409 if (save && SaveHist && HistFile) 410 save_history (hclass, s); 411 mutt_str_replace (&h->hist[h->last++], s); 412 if (h->last > HistSize) 413 h->last = 0; 414 } 415 } 416 h->cur = h->last; /* reset to the last entry */ 417} 418 419char *mutt_history_next (history_class_t hclass) 420{ 421 int next; 422 struct history *h = GET_HISTORY(hclass); 423 424 if (!HistSize || !h) 425 return (""); /* disabled */ 426 427 next = h->cur; 428 do 429 { 430 next++; 431 if (next > HistSize) 432 next = 0; 433 if (next == h->last) 434 break; 435 } while (h->hist[next] == NULL); 436 437 h->cur = next; 438 return (h->hist[h->cur] ? h->hist[h->cur] : ""); 439} 440 441char *mutt_history_prev (history_class_t hclass) 442{ 443 int prev; 444 struct history *h = GET_HISTORY(hclass); 445 446 if (!HistSize || !h) 447 return (""); /* disabled */ 448 449 prev = h->cur; 450 do 451 { 452 prev--; 453 if (prev < 0) 454 prev = HistSize; 455 if (prev == h->last) 456 break; 457 } while (h->hist[prev] == NULL); 458 459 h->cur = prev; 460 return (h->hist[h->cur] ? h->hist[h->cur] : ""); 461} 462 463void mutt_reset_history_state (history_class_t hclass) 464{ 465 struct history *h = GET_HISTORY(hclass); 466 467 if (!HistSize || !h) 468 return; /* disabled */ 469 470 h->cur = h->last; 471} 472 473int mutt_history_at_scratch (history_class_t hclass) 474{ 475 struct history *h = GET_HISTORY(hclass); 476 477 if (!HistSize || !h) 478 return 0; /* disabled */ 479 480 return h->cur == h->last; 481} 482 483void mutt_history_save_scratch (history_class_t hclass, const char *s) 484{ 485 struct history *h = GET_HISTORY(hclass); 486 487 if (!HistSize || !h) 488 return; /* disabled */ 489 490 /* Don't check if s has a value because the scratch buffer may contain 491 * an old garbage value that should be overwritten */ 492 mutt_str_replace (&h->hist[h->last], s); 493} 494 495static const char * 496history_format_str (char *dest, size_t destlen, size_t col, int cols, char op, const char *src, 497 const char *fmt, const char *ifstring, const char *elsestring, 498 unsigned long data, format_flag flags) 499{ 500 char *match = (char *)data; 501 502 switch (op) 503 { 504 case 's': 505 mutt_format_s (dest, destlen, fmt, match); 506 break; 507 } 508 509 return (src); 510} 511 512static void history_entry (char *s, size_t slen, MUTTMENU *m, int num) 513{ 514 char *entry = ((char **)m->data)[num]; 515 516 mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, "%s", history_format_str, 517 (unsigned long) entry, MUTT_FORMAT_ARROWCURSOR); 518} 519 520static void history_menu (char *buf, size_t buflen, char **matches, int match_count) 521{ 522 MUTTMENU *menu; 523 int done = 0; 524 char helpstr[LONG_STRING]; 525 char title[STRING]; 526 527 snprintf (title, sizeof (title), _("History '%s'"), buf); 528 529 menu = mutt_new_menu (MENU_GENERIC); 530 menu->make_entry = history_entry; 531 menu->title = title; 532 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_GENERIC, HistoryHelp); 533 mutt_push_current_menu (menu); 534 535 menu->max = match_count; 536 menu->data = matches; 537 538 while (!done) 539 { 540 switch (mutt_menuLoop (menu)) 541 { 542 case OP_GENERIC_SELECT_ENTRY: 543 strfcpy (buf, matches[menu->current], buflen); 544 /* fall through */ 545 546 case OP_EXIT: 547 done = 1; 548 break; 549 } 550 } 551 552 mutt_pop_current_menu (menu); 553 mutt_menuDestroy (&menu); 554} 555 556static int search_history (char *search_buf, history_class_t hclass, char **matches) 557{ 558 struct history *h = GET_HISTORY(hclass); 559 int match_count = 0, cur; 560 561 if (!HistSize || !h) 562 return 0; 563 564 cur = h->last; 565 do 566 { 567 cur--; 568 if (cur < 0) 569 cur = HistSize; 570 if (cur == h->last) 571 break; 572 if (mutt_stristr (h->hist[cur], search_buf)) 573 matches[match_count++] = h->hist[cur]; 574 } while (match_count < HistSize); 575 576 return match_count; 577} 578 579void mutt_history_complete (char *buf, size_t buflen, history_class_t hclass) 580{ 581 char **matches; 582 int match_count; 583 584 matches = safe_calloc (HistSize, sizeof (char *)); 585 match_count = search_history (buf, hclass, matches); 586 if (match_count) 587 { 588 if (match_count == 1) 589 strfcpy (buf, matches[0], buflen); 590 else 591 history_menu (buf, buflen, matches, match_count); 592 } 593 FREE(&matches); 594}