mutt stable branch with some hacks
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
26struct history
27{
28 char **hist;
29 short cur;
30 short last;
31};
32
33/* global vars used for the string-history routines */
34
35static struct history History[HC_LAST];
36static int OldSize = 0;
37
38#define GET_HISTORY(CLASS) ((CLASS >= HC_LAST) ? NULL : &History[CLASS])
39
40static void init_history (struct history *h)
41{
42 int i;
43
44 if(OldSize)
45 {
46 if (h->hist)
47 {
48 for (i = 0 ; i <= OldSize ; i ++)
49 FREE (&h->hist[i]);
50 FREE (&h->hist);
51 }
52 }
53
54 if (HistSize)
55 h->hist = safe_calloc (HistSize + 1, sizeof (char *));
56
57 h->cur = 0;
58 h->last = 0;
59}
60
61void mutt_read_histfile (void)
62{
63 FILE *f;
64 int line = 0, hclass, read;
65 char *linebuf = NULL, *p;
66 size_t buflen;
67
68 if ((f = fopen (HistFile, "r")) == NULL)
69 return;
70
71 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL)
72 {
73 read = 0;
74 if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 ||
75 *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0)
76 {
77 mutt_error (_("Bad history file format (line %d)"), line);
78 break;
79 }
80 /* silently ignore too high class (probably newer mutt) */
81 if (hclass >= HC_LAST)
82 continue;
83 *p = '\0';
84 p = safe_strdup (linebuf + read);
85 if (p)
86 {
87 mutt_convert_string (&p, "utf-8", Charset, 0);
88 mutt_history_add (hclass, p, 0);
89 FREE (&p);
90 }
91 }
92
93 safe_fclose (&f);
94 FREE (&linebuf);
95}
96
97static void shrink_histfile (void)
98{
99 char tmpfname[_POSIX_PATH_MAX];
100 FILE *f, *tmp = NULL;
101 int n[HC_LAST] = { 0 };
102 int line, hclass;
103 char *linebuf = NULL;
104 size_t buflen;
105
106 if ((f = fopen (HistFile, "r")) == NULL)
107 return;
108
109 line = 0;
110 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL)
111 {
112 if (sscanf (linebuf, "%d", &hclass) < 1 || hclass < 0)
113 {
114 mutt_error (_("Bad history file format (line %d)"), line);
115 goto cleanup;
116 }
117 /* silently ignore too high class (probably newer mutt) */
118 if (hclass >= HC_LAST)
119 continue;
120 n[hclass]++;
121 }
122
123 for(hclass = HC_FIRST; hclass < HC_LAST; hclass++)
124 if (n[hclass] > SaveHist)
125 {
126 mutt_mktemp (tmpfname, sizeof (tmpfname));
127 if ((tmp = safe_fopen (tmpfname, "w+")) == NULL)
128 mutt_perror (tmpfname);
129 break;
130 }
131
132 if (tmp != NULL)
133 {
134 rewind (f);
135 line = 0;
136 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL)
137 {
138 if (sscanf (linebuf, "%d", &hclass) < 1 || hclass < 0)
139 {
140 mutt_error (_("Bad history file format (line %d)"), line);
141 goto cleanup;
142 }
143 /* silently ignore too high class (probably newer mutt) */
144 if (hclass >= HC_LAST)
145 continue;
146 if (n[hclass]-- <= SaveHist)
147 fprintf (tmp, "%s\n", linebuf);
148 }
149 }
150
151cleanup:
152 safe_fclose (&f);
153 FREE (&linebuf);
154 if (tmp != NULL)
155 {
156 if (fflush (tmp) == 0 &&
157 (f = fopen (HistFile, "w")) != NULL) /* __FOPEN_CHECKED__ */
158 {
159 rewind (tmp);
160 mutt_copy_stream (tmp, f);
161 safe_fclose (&f);
162 }
163 safe_fclose (&tmp);
164 unlink (tmpfname);
165 }
166}
167
168static void save_history (history_class_t hclass, const char *s)
169{
170 static int n = 0;
171 FILE *f;
172 char *tmp, *p;
173
174 if (!s || !*s) /* This shouldn't happen, but it's safer. */
175 return;
176
177 if ((f = fopen (HistFile, "a")) == NULL)
178 {
179 mutt_perror ("fopen");
180 return;
181 }
182
183 tmp = safe_strdup (s);
184 mutt_convert_string (&tmp, Charset, "utf-8", 0);
185
186 /* Format of a history item (1 line): "<histclass>:<string>|".
187 We add a '|' in order to avoid lines ending with '\'. */
188 fprintf (f, "%d:", (int) hclass);
189 for (p = tmp; *p; p++)
190 {
191 /* Don't copy \n as a history item must fit on one line. The string
192 shouldn't contain such a character anyway, but as this can happen
193 in practice, we must deal with that. */
194 if (*p != '\n')
195 putc ((unsigned char) *p, f);
196 }
197 fputs ("|\n", f);
198
199 safe_fclose (&f);
200 FREE (&tmp);
201
202 if (--n < 0)
203 {
204 n = SaveHist;
205 shrink_histfile();
206 }
207}
208
209void mutt_init_history(void)
210{
211 history_class_t hclass;
212
213 if (HistSize == OldSize)
214 return;
215
216 for(hclass = HC_FIRST; hclass < HC_LAST; hclass++)
217 init_history(&History[hclass]);
218
219 OldSize = HistSize;
220}
221
222void mutt_history_add (history_class_t hclass, const char *s, int save)
223{
224 int prev;
225 struct history *h = GET_HISTORY(hclass);
226
227 if (!HistSize || !h)
228 return; /* disabled */
229
230 if (*s)
231 {
232 prev = h->last - 1;
233 if (prev < 0) prev = HistSize;
234
235 /* don't add to prompt history:
236 * - lines beginning by a space
237 * - repeated lines
238 */
239 if (*s != ' ' && (!h->hist[prev] || mutt_strcmp (h->hist[prev], s) != 0))
240 {
241 if (save && SaveHist)
242 save_history (hclass, s);
243 mutt_str_replace (&h->hist[h->last++], s);
244 if (h->last > HistSize)
245 h->last = 0;
246 }
247 }
248 h->cur = h->last; /* reset to the last entry */
249}
250
251char *mutt_history_next (history_class_t hclass)
252{
253 int next;
254 struct history *h = GET_HISTORY(hclass);
255
256 if (!HistSize || !h)
257 return (""); /* disabled */
258
259 next = h->cur + 1;
260 if (next > HistSize)
261 next = 0;
262 if (h->hist[next] || (next == h->last))
263 h->cur = next;
264 else
265 h->cur = 0;
266 return (h->hist[h->cur] ? h->hist[h->cur] : "");
267}
268
269char *mutt_history_prev (history_class_t hclass)
270{
271 int prev;
272 struct history *h = GET_HISTORY(hclass);
273
274 if (!HistSize || !h)
275 return (""); /* disabled */
276
277 prev = h->cur - 1;
278 if (prev < 0)
279 {
280 prev = HistSize;
281 while ((prev > 0) && (prev != h->last) && (h->hist[prev] == NULL))
282 prev--;
283 }
284 if (h->hist[prev] || (prev == h->last))
285 h->cur = prev;
286 return (h->hist[h->cur] ? h->hist[h->cur] : "");
287}
288
289void mutt_reset_history_state (history_class_t hclass)
290{
291 struct history *h = GET_HISTORY(hclass);
292
293 if (!HistSize || !h)
294 return; /* disabled */
295
296 h->cur = h->last;
297}
298
299int mutt_history_at_scratch (history_class_t hclass)
300{
301 struct history *h = GET_HISTORY(hclass);
302
303 if (!HistSize || !h)
304 return 0; /* disabled */
305
306 return h->cur == h->last;
307}
308
309void mutt_history_save_scratch (history_class_t hclass, const char *s)
310{
311 struct history *h = GET_HISTORY(hclass);
312
313 if (!HistSize || !h)
314 return; /* disabled */
315
316 /* Don't check if s has a value because the scratch buffer may contain
317 * an old garbage value that should be overwritten */
318 mutt_str_replace (&h->hist[h->last], s);
319}