mailx: don't select current msg when using x-y
[mailx.git] / mailx.c
blob8851a1e91588d158a6c687060ac5ecb8de90f8da
1 #include <ctype.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include "config.h"
8 #include "mbox.h"
9 #include "sort.h"
10 #include "util.h"
12 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
13 #define MIN(a, b) ((a) < (b) ? (a) : (b))
14 #define MAILBUF (1 << 16)
15 #define MAXLINE (1 << 7)
16 #define BUFSIZE (1 << 12)
18 static struct mbox *mbox;
19 static struct sort *sort;
20 static int cur;
21 static int sel[MAXMAILS];
22 static int nsel;
24 static int read_line(char *dst, int size)
26 static char buf[BUFSIZE];
27 static int cur;
28 static int len;
29 int nw = 0;
30 while (1) {
31 int cur_len = MIN(len - cur, size - nw - 1);
32 char *nl = memchr(buf + cur, '\n', cur_len);
33 int nr = nl ? nl - buf - cur + 1 : cur_len;
34 if (nr) {
35 memcpy(dst + nw, buf + cur, nr);
36 nw += nr;
37 cur += nr;
38 dst[nw] = '\0';
40 if (nl || nw == size - 1)
41 return nw;
42 cur = 0;
43 if ((len = read(STDIN_FILENO, buf, BUFSIZE)) <= 0)
44 return -1;
46 return nw;
49 static char *till_eol(char *r, int len, char *s)
51 char *d = r + len;
52 while (r < d && *s && *s != '\r' && *s != '\n')
53 *r++ = *s++;
54 return r;
57 static char *put_mem(char *dst, char *src, int len)
59 memcpy(dst, src, len);
60 return dst + len;
63 static char *cut_word(char *dst, char *s)
65 while (*s && isspace(*s))
66 s++;
67 while (*s && !isspace(*s))
68 *dst++ = *s++;
69 *dst = '\0';
70 return s;
73 static int msg_num(char *num)
75 int n = -1;
76 if (!*num || !strcmp(".", num))
77 n = cur;
78 if (isdigit(*num))
79 n = atoi(num);
80 if (!strcmp("$", num))
81 n = mbox->n - 1;
82 if (!strcmp(",", num)) {
83 int i = cur;
84 while (++i < sort->n) {
85 if (!(sort->mails[i]->stat & STAT_READ)) {
86 n = i;
87 break;
91 if (n < 0 || n >= mbox->n)
92 return -1;
93 return n;
96 static int search(char *args, int all)
98 static char hdr_name[MAXLINE];
99 static char hdr_val[MAXLINE];
100 char *beg = strchr(args, '(');
101 char *spc = beg ? strchr(beg, ' ') : NULL;
102 char *end = spc ? strchr(spc, ')') : NULL;
103 int i;
104 if (beg && (!end || !spc)) {
105 return 0;
107 if (beg) {
108 int hdr_len = spc - beg - 1;
109 put_mem(hdr_name, beg + 1, hdr_len);
110 hdr_name[hdr_len] = '\0';
111 while (isspace(*spc))
112 spc++;
113 put_mem(hdr_val, spc, end - spc);
114 hdr_val[end - spc] = '\0';
116 for (i = all ? 0 : cur + 1; i < sort->n; i++) {
117 char *hdr = mail_hdr(sort->mails[i], hdr_name);
118 char *s = hdr + strlen(hdr_name);
119 char *d = hdr + hdr_len(hdr);
120 while (s && s < d) {
121 if (!strncmp(hdr_val, s, strlen(hdr_val))) {
122 if (!all) {
123 cur = i;
124 return 1;
126 sel[nsel++] = i;
127 break;
129 s = memchr(s + 1, hdr_val[0], d - s - 1);
132 return nsel;
135 static int sel_msgs(char *args, int all)
137 char num[MAXLINE];
138 int i;
139 nsel = 0;
140 while (1) {
141 char *dash;
142 int beg = -1;
143 int end = -1;
144 while (isspace(*args))
145 args++;
146 if (*args == '/') {
147 return search(args, all);
149 args = cut_word(num, args);
150 if (nsel && !*num)
151 break;
152 if ((dash = strchr(num, '-'))) {
153 char beg_str[MAXLINE];
154 memcpy(beg_str, num, dash - num);
155 beg_str[dash - num] = '\0';
156 beg = msg_num(beg_str);
157 end = msg_num(dash + 1);
158 } else {
159 beg = msg_num(num);
160 end = beg;
162 if (beg != -1 && end != -1) {
163 if (!all) {
164 cur = beg;
165 return 1;
167 for (i = beg; i <= end; i++)
168 sel[nsel++] = i;
171 return nsel;
174 static void cmd_page(char *args)
176 struct mail *mail;
177 char buf[MAILBUF];
178 char *pg_args[] = {PAGER, NULL};
179 char *s = buf;
180 if (!sel_msgs(args, 0))
181 return;
182 mail = sort->mails[cur];
183 mail->stat |= STAT_READ;
184 s += mail_head(mail, buf, sizeof(buf), hdr_filt, ARRAY_SIZE(hdr_filt));
185 s = put_mem(s, mail->body, MIN(sizeof(buf) - (s - buf),
186 mail->body_len));
187 exec_pipe(PAGER, pg_args, buf, s - buf);
190 static char *cut_cmd(char *dst, char *s)
192 while (isalpha(*s)) {
193 *dst++ = *s++;
195 *dst = '\0';
196 return s;
199 static char *put_int(char *s, int n, int w)
201 int i;
202 for (i = 0; i < w; i++) {
203 s[w - i - 1] = n || !i ? '0' + n % 10 : ' ';
204 n = n / 10;
206 return s + w;
209 static char *put_str(char *dst, char *src)
211 int len = strchr(src, '\0') - src;
212 memcpy(dst, src, len + 1);
213 return dst + len;
216 static char *put_hdr(struct mail *mail, char *name, int w, char *s)
218 char *hdr = mail_hdr(mail, name);
219 return till_eol(s, w, hdr ? hdr + strlen(name) + 1 : NULL);
222 static void cmd_head(char *args)
224 int beg, end;
225 int i;
226 if (!sel_msgs(args, 0))
227 return;
228 beg = cur / NHEAD * NHEAD;
229 end = MIN(beg + NHEAD, mbox->n);
230 for (i = beg; i < end; i++) {
231 struct mail *mail = sort->mails[i];
232 char fmt[MAXLINE];
233 char *s = fmt;
234 *s++ = i == cur ? '>' : ' ';
235 if (mail->stat & STAT_DEL)
236 *s++ = 'D';
237 else
238 *s++ = ' ';
239 if (mail->stat & STAT_READ)
240 *s++ = ' ';
241 else
242 *s++ = mail->stat & STAT_NEW ? 'N' : 'O';
243 s = put_int(s, i, DIGITS);
244 *s++ = ' ';
245 *s++ = ' ';
246 s = put_hdr(mail, "From:", 16, s);
247 *s++ = ' ';
248 *s++ = ' ';
249 s = sort_draw(sort, i, s, WIDTH - (s - fmt));
250 s = put_hdr(mail, "Subject:", WIDTH - (s - fmt), s);
251 *s++ = '\n';
252 write(STDOUT_FILENO, fmt, s - fmt);
256 static void cmd_z(char *args)
258 char num[MAXLINE];
259 int page = -1;
260 cut_word(num, args);
261 if (!*num)
262 page = MIN(cur + NHEAD, mbox->n) / NHEAD;
263 if (*num == '+' || *num == '-') {
264 int d = (*num + 1) ? (*num == '-' ? -1 : 1) : atoi(num);
265 page = cur / NHEAD + d;
267 if (*num == '$')
268 page = mbox->n / NHEAD;
269 if (isdigit(*num))
270 page = atoi(num);
271 if (page >= 0 && page * NHEAD < mbox->n) {
272 cur = page * NHEAD;
273 cmd_head("");
277 static void cmd_del(char *args)
279 int i;
280 if (!sel_msgs(args, 1))
281 return;
282 for (i = 0; i < nsel; i++)
283 sort->mails[sel[i]]->stat |= STAT_DEL;
286 static void cmd_undel(char *args)
288 int i;
289 if (!sel_msgs(args, 1))
290 return;
291 for (i = 0; i < nsel; i++)
292 sort->mails[sel[i]]->stat &= ~STAT_DEL;
295 static void print(char *s)
297 write(STDOUT_FILENO, s, strlen(s));
300 static void open_mbox(char *filename)
302 mbox = mbox_alloc(filename);
303 sort = sort_alloc(mbox);
304 cur = 0;
307 static void close_mbox(void)
309 sort_free(sort);
310 mbox_free(mbox);
313 static int is_mbox(char *filename)
315 struct stat st;
316 stat(filename, &st);
317 return S_ISREG(st.st_mode);
320 static void sum_mbox(void)
322 char msg[MAXLINE];
323 char *s = put_int(msg, mbox->n, DIGITS);
324 int new = 0;
325 int unread = 0;
326 int del = 0;
327 int i;
328 s = put_str(s, " messages ");
329 for (i = 0; i < mbox->n; i++) {
330 if (mbox->mails[i].stat & STAT_NEW)
331 new++;
332 if (!(mbox->mails[i].stat & STAT_READ))
333 unread++;
334 if (mbox->mails[i].stat & STAT_DEL)
335 del++;
337 if (new) {
338 s = put_int(s, new, DIGITS);
339 s = put_str(s, " new ");
341 if (unread) {
342 s = put_int(s, unread, DIGITS);
343 s = put_str(s, " unread ");
345 if (del) {
346 s = put_int(s, del, DIGITS);
347 s = put_str(s, " deleted ");
349 s = put_str(s, "\n");
350 print(msg);
353 static int has_mail(char *path)
355 struct stat st;
356 if (stat(path, &st) == -1)
357 return 0;
358 return st.st_mtime > st.st_atime;
361 static int mbox_path(char *path, char *addr)
363 char *s = path;
364 int i;
365 if (*addr == '+') {
366 s = put_str(s, FOLDER);
367 s = put_str(s, addr + 1);
369 if (*addr == ',') {
370 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
371 if (has_mail(boxes[i])) {
372 s = put_str(s, boxes[i]);
373 break;
377 if (!strcmp(".", addr) && mbox)
378 s = put_str(s, mbox->path);
379 if (s == path && *addr)
380 s = put_str(s, addr);
381 return s - path;
384 static void cmd_fold(char *args)
386 char filename[MAXLINE];
387 args = cut_word(filename, args);
388 if (*filename) {
389 char path[MAXPATHLEN];
390 if (mbox_path(path, filename) && is_mbox(path)) {
391 close_mbox();
392 open_mbox(filename);
394 } else {
395 sum_mbox();
399 static void warn_nomem(void)
401 print("no mem for new msgs\n");
404 static void cmd_inc(char *args)
406 char msg[MAXLINE];
407 int new = mbox_inc(mbox);
408 char *s;
409 int i;
410 if (new == -1)
411 warn_nomem();
412 if (new > 0) {
413 sort_inc(sort);
414 s = put_int(msg, new, DIGITS);
415 s = put_str(s, " new\n");
416 print(msg);
418 for (i = 0; i < ARRAY_SIZE(boxes); i++) {
419 if (has_mail(boxes[i])) {
420 char *box = strrchr(boxes[i], '/');
421 s = msg;
422 box = box ? box + 1 : boxes[i];
423 s = put_str(s, "mbox: ");
424 s = put_str(s, box);
425 s = put_str(s, "\n");
426 print(msg);
431 static void cmd_next(char *args)
433 char num[MAXLINE];
434 cut_word(num, args);
435 if (!*num) {
436 if (cur + 1 < mbox->n) {
437 cur++;
438 } else {
439 print("EOF\n");
440 return;
443 cmd_page(args);
446 static void prompt(void)
448 write(STDOUT_FILENO, "? ", 2);
451 static void loop(void)
453 char line[MAXLINE];
454 char cmd[MAXLINE];
455 prompt();
456 while (read_line(line, sizeof(line)) > 0) {
457 char *args = cut_cmd(cmd, line);
458 int len = strlen(cmd);
459 if (!len) {
460 cmd_next(args);
461 prompt();
462 continue;
464 if (!strncmp("page", cmd, len))
465 cmd_page(args);
466 if (!strncmp("next", cmd, len))
467 cmd_next(args);
468 if (!strncmp("header", cmd, len))
469 cmd_head(args);
470 if (!strncmp("file", cmd, len) || !strncmp("folder", cmd, len))
471 cmd_fold(args);
472 if (!strncmp("inc", cmd, len))
473 cmd_inc(args);
474 if (!strncmp("z", cmd, len))
475 cmd_z(args);
476 if (!strncmp("delete", cmd, len) || !strncmp("rm", cmd, len))
477 cmd_del(args);
478 if (!strncmp("undelete", cmd, len))
479 cmd_undel(args);
480 if (!strncmp("quit", cmd, len)) {
481 if (mbox_write(mbox) == -1)
482 warn_nomem();
483 else
484 return;
486 if (!strncmp("xit", cmd, len) || !strncmp("exit", cmd, len))
487 return;
488 prompt();
492 int main(int argc, char *argv[])
494 int i = 0;
495 char *filename = NULL;
496 while (++i < argc)
497 if (!strcmp("-f", argv[i]))
498 filename = argv[++i];
499 if (filename) {
500 char path[MAXPATHLEN];
501 if (mbox_path(path, filename) && is_mbox(path)) {
502 open_mbox(path);
503 loop();
504 close_mbox();
507 return 0;