Fall back on index for sort if the due date is the same, not just if both due dates...
[tdl.git] / inter.c
CommitLineData
7024e37b
JW
1/*
2 $Header: /cvs/src/tdl/inter.c,v 1.13.2.1 2004/01/07 00:09:05 richard Exp $
3
4 tdl - A console program for managing to-do lists
5 Copyright (C) 2001-2004 Richard P. Curnow
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20 */
21
22/* This file deals with interactive mode */
23
24#include "tdl.h"
25#include <assert.h>
26#include <ctype.h>
27#include <stdio.h>
28#include <unistd.h>
29
30#ifdef USE_READLINE
31#if BARE_READLINE_H
32#include <readline.h>
33#include <history.h>
34#else
35#include <readline/readline.h>
36#include <readline/history.h>
37#endif
38
39#if USE_RL_COMPLETION_MATCHES
40#define COMPLETION_MATCHES rl_completion_matches
41#else
42#define COMPLETION_MATCHES completion_matches
43#endif
44
45#endif
46
47
48#ifdef USE_READLINE
49static char *generate_a_command_completion(const char *text, int state)/*{{{*/
50{
51 static int list_index, len;
52 char *name;
53
54 if (!state) {
55 list_index = 0;
56 len = strlen(text);
57 }
58
59 while (list_index < n_cmds) {
60 int inter_ok = cmds[list_index].interactive_ok;
61 name = cmds[list_index].name;
62 list_index++;
63 if (inter_ok && !strncmp(name, text, len)) {
64 return new_string(name);
65 }
66 }
67
68 return NULL; /* For no matches */
69}
70/*}}}*/
71static char *generate_a_priority_completion(const char *text, int state)/*{{{*/
72{
73 static char *priorities[] = {"urgent", "high", "normal", "low", "verylow", NULL};
74 static int list_index, len;
75 char *name;
76
77 if (!state) {
78 list_index = 0;
79 len = strlen(text);
80 }
81
82 while ((name = priorities[list_index])) {
83 list_index++;
84 if (!strncmp(name, text, len)) {
85 return new_string(name);
86 }
87 }
88
89 return NULL; /* For no matches */
90}
91/*}}}*/
92
93/* Structure to build a single linked list of nodes, working towards the
94 * ancestor. */
95struct node_chain {
96 struct node_chain *next;
97 struct node *node;
98 struct node *start; /* To see if we've got back to the start of the ring */
99 int index;
100};
101
102static struct node_chain *start_chain(struct links *x, struct node_chain *parent)/*{{{*/
103{
104 struct node_chain *result;
105 result = new(struct node_chain);
106 result->start = (struct node *) x;
107 result->node = x->next;
108 result->index = 1;
109 result->next = parent;
110 return result;
111}
112/*}}}*/
113static void cleanup_chain(struct node_chain *chain)/*{{{*/
114{
115 /* TODO: Reclaim memory */
116 return;
117}
118/*}}}*/
119static char *format_index(struct node_chain *chain)/*{{{*/
120{
121 int indices[256];
122 int n, first;
123 struct node_chain *x;
124 char buf[1024], buf1[32];
125
126 for (n=0, x=chain; x; n++, x=x->next) {
127 indices[n] = x->index;
128 }
129 buf[0] = '\0';
130 first = 1;
131 while (--n >= 0) {
132 sprintf(buf1, "%d", indices[n]);
133 if (!first) {
134 strcat(buf, ".");
135 } else {
136 first = 0;
137 }
138 strcat (buf, buf1);
139 }
140 return new_string(buf);
141}
142/*}}}*/
143static struct node *advance_chain(struct node_chain **pchain, char **index_string)/*{{{*/
144{
145 struct node_chain *chain = *pchain;
146 struct node *result = NULL;
147
148#if DEBUG_ADVANCE
149 fprintf(stderr, "advance chain, top index=%d\n", chain->index);
150#endif
151
152 while (1) {
153 if (chain->node == chain->start) {
154 struct node_chain *next = chain->next;
155 free(chain);
156 chain = next;
157 if (chain) {
158 chain->node = chain->node->chain.next;
159 ++chain->index;
160#if DEBUG_ADVANCE
161 fprintf(stderr, "Returning to outer level, index=%d\n", chain->index);
162#endif
163 continue;
164 } else {
165#if DEBUG_ADVANCE
166 fprintf(stderr, "Got to outermost level\n");
167#endif
168 result = NULL;
169 break;
170 }
171 } else if (has_kids(chain->node)) {
172#if DEBUG_ADVANCE
173 fprintf(stderr, "Node has children, scanning children next\n");
174#endif
175 result = chain->node;
176 *index_string = format_index(chain);
177 /* Set-up to visit kids next time */
178 chain = start_chain(&chain->node->kids, chain);
179 break;
180 } else {
181 /* Ordinary case */
182#if DEBUG_ADVANCE
183 fprintf(stderr, "ordinary case\n");
184#endif
185 result = chain->node;
186 *index_string = format_index(chain);
187 chain->node = chain->node->chain.next;
188 ++chain->index;
189 break;
190 }
191 }
192
193 *pchain = chain;
194 return result;
195}
196/*}}}*/
197static char *generate_a_done_completion(const char *text, int state)/*{{{*/
198{
199 static struct node_chain *chain = NULL;
200 struct node *node;
201 struct node *narrow_top;
202 char *buf;
203 int len;
204
205 load_database_if_not_loaded();
206
207 if (!state) {
208 /* Re-initialise the node chain */
209 if (chain) cleanup_chain(chain);
210 narrow_top = get_narrow_top();
211 chain = start_chain((narrow_top ? &narrow_top->kids : &top), NULL);
212 }
213
214 len = strlen(text);
215 while ((node = advance_chain(&chain, &buf))) {
216 int undone = (!node->done);
217 if (undone && !strncmp(text, buf, len)) {
218 return buf;
219 } else {
220 /* Avoid gross memory leak */
221 free(buf);
222 }
223 }
224 return NULL;
225}
226/*}}}*/
227
228/* Can't pass extra args thru to completers :-( */
229static int want_postponed_entries = 0;
230
231static char *generate_a_postpone_completion(const char *text, int state)/*{{{*/
232{
233 static struct node_chain *chain = NULL;
234 struct node *node;
235 struct node *narrow_top;
236 char *buf;
237 int len;
238
239 load_database_if_not_loaded();
240
241 if (!state) {
242 /* Re-initialise the node chain */
243 if (chain) cleanup_chain(chain);
244 narrow_top = get_narrow_top();
245 chain = start_chain((narrow_top ? &narrow_top->kids : &top), NULL);
246 }
247
248 len = strlen(text);
249 while ((node = advance_chain(&chain, &buf))) {
250 int is_done = (node->done > 0);
251 int not_postponed = (node->arrived != POSTPONED_TIME);
252 if (!is_done && (not_postponed ^ want_postponed_entries) && !strncmp(text, buf, len)) {
253 return buf;
254 } else {
255 /* Avoid gross memory leak */
256 free(buf);
257 }
258 }
259 return NULL;
260}
261/*}}}*/
262
263char **complete_help(char *text, int index)/*{{{*/
264{
265 char **matches;
266 matches = COMPLETION_MATCHES(text, generate_a_command_completion);
267 return matches;
268}
269/*}}}*/
270char **default_completer(char *text, int index)/*{{{*/
271{
272 if (cmds[index].synopsis) {
273 fprintf(stderr, "\n%s %s\n", cmds[index].name, cmds[index].synopsis);
274 rl_on_new_line();
275 }
276 return NULL;
277}
278/*}}}*/
279char **complete_list(char *text, int index)/*{{{*/
280{
281 char **matches;
282 if (text[0] && isalpha(text[0])) {
283 /* Try to complete priority */
284 matches = COMPLETION_MATCHES(text, generate_a_priority_completion);
285 return matches;
286 } else {
287 return default_completer(text, index);
288 }
289}
290/*}}}*/
291char **complete_priority(char *text, int index)/*{{{*/
292{
293 return complete_list(text, index);
294}
295/*}}}*/
296char **complete_postpone(char *text, int index)/*{{{*/
297{
298 char **matches;
299 want_postponed_entries = 0;
300 matches = COMPLETION_MATCHES(text, generate_a_postpone_completion);
301 return matches;
302}
303/*}}}*/
304char **complete_open(char *text, int index)/*{{{*/
305{
306 char **matches;
307 want_postponed_entries = 1;
308 matches = COMPLETION_MATCHES(text, generate_a_postpone_completion);
309 return matches;
310}
311/*}}}*/
312char **complete_done(char *text, int index)/*{{{*/
313{
314 char **matches;
315 matches = COMPLETION_MATCHES(text, generate_a_done_completion);
316 return matches;
317}
318/*}}}*/
319
320static char **tdl_completion(char *text, int start, int end)/*{{{*/
321{
322 char **matches = NULL;
323 if (start == 0) {
324 matches = COMPLETION_MATCHES(text, generate_a_command_completion);
325 } else {
326 int i;
327
328 for (i=0; i<n_cmds; i++) {
329 if (!strncmp(rl_line_buffer, cmds[i].name, cmds[i].matchlen)) {
330 if (cmds[i].completer) {
331 matches = (cmds[i].completer)(text, i);
332 } else {
333 matches = default_completer(text, i);
334 }
335 break;
336 }
337 }
338 }
339
340 return matches;
341}
342/*}}}*/
343static char **null_tdl_completion(char *text, int start, int end)/*{{{*/
344{
345 return NULL;
346}
347/*}}}*/
348#else
349char **complete_help(char *text, int index)/*{{{*/
350{
351 return NULL;
352}/*}}}*/
353char **complete_list(char *text, int index)/*{{{*/
354{
355 return NULL;
356}/*}}}*/
357char **complete_priority(char *text, int index)/*{{{*/
358{
359 return NULL;
360}/*}}}*/
361char **complete_postpone(char *text, int index)/*{{{*/
362{
363 return NULL;
364}
365/*}}}*/
366char **complete_open(char *text, int index)/*{{{*/
367{
368 return NULL;
369}
370/*}}}*/
371char **complete_done(char *text, int index)/*{{{*/
372{
373 return NULL;
374}
375/*}}}*/
376#endif
377
378static void add_null_arg(char ***av, int *max, int *n)/*{{{*/
379{
380 if (*max == *n) {
381 *max += 4;
382 *av = grow_array(char *, *max, *av);
383 }
384 (*av)[*n] = NULL;
385 ++*n;
386 return;
387}
388/*}}}*/
389static void add_arg(char ***av, int *max, int *n, char *p, int len)/*{{{*/
390{
391 char *u, *v;
392 int nn;
393
394 if (*max == *n) {
395 *max += 4;
396 *av = grow_array(char *, *max, *av);
397 }
398
399 (*av)[*n] = new_array(char, len + 1);
400 /* Make copy of string, rejecting any escape characters (backslashes) */
401 for (u=p, v=(*av)[*n], nn=len; nn>0; ) {
402 if (*u == '\\') {
403 u++, nn--;
404 switch(*u) {
405 case 'n':
406 *v++ = '\n';
407 u++, nn--;
408 break;
409 case 't':
410 *v++ = '\t';
411 u++, nn--;
412 break;
413 default:
414 *v++ = *u++;
415 nn--;
416 break;
417 }
418 } else {
419 *v++ = *u++;
420 nn--;
421 }
422 }
423 *v = 0;
424 ++*n;
425}
426/*}}}*/
427
428static char **argv = NULL;
429
430static void split_line_and_dispatch(char *line)/*{{{*/
431{
432 static int max = 0;
433 int i, n;
434 char *av0 = "tdl";
435 char *p, *q, t;
436
437 /* Poke in argv[0] */
438 n = 0;
439 add_arg(&argv, &max, &n, av0, strlen(av0));
440
441 /* Now, split the line and add each argument */
442 p = line;
443 for (;;) {
444 while (*p && isspace(*p)) p++;
445 if (!*p) break;
446
447 /* p points to start of argument. */
448 if (*p == '\'' || *p == '"') {
449 t = *p;
450 p++;
451 /* Scan for matching terminator */
452 q = p + 1;
453 while (*q && (*q != t)) {
454 if (*q == '\\') q++; /* Escape following character */
455 if (*q) q++; /* Bogus backslash at end of line */
456 }
457 } else {
458 /* Single word arg */
459 q = p + 1;
460 while (*q && !isspace(*q)) q++;
461 }
462 add_arg(&argv, &max, &n, p, q - p);
463 if (!*q) break;
464 p = q + 1;
465 }
466
467 add_null_arg(&argv, &max, &n);
468#ifdef TEST
469 /* For now, print arg list for debugging */
470 {
471 int i;
472 for (i=0; argv[i]; i++) {
473 printf("arg %d : <%s>\n", i, argv[i]);
474 }
475 fflush(stdout);
476 }
477#else
478 /* Now dispatch */
479 dispatch(argv);
480#endif
481
482 /* Now free arguments */
483 for (i=0; argv[i]; i++) {
484 free(argv[i]);
485 }
486
487 return;
488}
489/*}}}*/
490static int is_line_blank(char *line)/*{{{*/
491{
492 char *p;
493 for (p=line; *p; p++) {
494 if (!isspace(*p)) return 0;
495 }
496 return 1;
497}
498/*}}}*/
499static char *make_prompt(void)/*{{{*/
500{
501 char *narrow_prefix = get_narrow_prefix();
502 char *result;
503 if (narrow_prefix) {
504 int length;
505 length = strlen(narrow_prefix) + 8;
506 result = new_array(char, length);
507 strcpy(result, "tdl[");
508 strcat(result, narrow_prefix);
509 strcat(result, "]> ");
510 } else {
511 result = new_string("tdl> ");
512 }
513 return result;
514}
515/*}}}*/
516#ifdef USE_READLINE
517static void interactive_readline(void)/*{{{*/
518{
519 char *cmd;
520 int had_char;
521
522 do {
523 char *prompt = make_prompt();
524 cmd = readline(prompt);
525 free(prompt);
526
527 /* At end of file (e.g. input is a script, or user hits ^D, or stdin (file
528 #0) is closed by the signal handler) */
529 if (!cmd) return;
530
531 /* Check if line is blank */
532 had_char = !is_line_blank(cmd);
533
534 if (had_char) {
535 add_history(cmd);
536 split_line_and_dispatch(cmd);
537 free(cmd);
538 }
539 } while (1);
540
541 return;
542
543}
544/*}}}*/
545
546static char *readline_initval = NULL;
547static int setup_initval_hook(void)/*{{{*/
548{
549 if (readline_initval) {
550 rl_insert_text(readline_initval);
551 rl_redisplay();
552 }
553 return 0;
554}
555/*}}}*/
556static char *interactive_text_readline(char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
557{
558 char *line;
559 Function *old_rl_pre_input_hook = NULL;
560
561 *error = 0;
562 old_rl_pre_input_hook = rl_pre_input_hook;
563 if (initval) {
564 readline_initval = initval;
565 rl_pre_input_hook = setup_initval_hook;
566 }
567 line = readline(prompt);
568 rl_pre_input_hook = old_rl_pre_input_hook;
569 readline_initval = NULL;
570 *is_blank = line ? is_line_blank(line) : 1;
571 if (line && !*is_blank) add_history(line);
572 return line;
573}
574/*}}}*/
575#endif
576static char *get_line_stdio(char *prompt, int *at_eof)/*{{{*/
577{
578 int n, max = 0;
579 char *line = NULL;
580 int c;
581
582 *at_eof = 0;
583 printf("%s",prompt);
584 fflush(stdout);
585 n = 0;
586 do {
587 c = getchar();
588 if (c == EOF) {
589 *at_eof = 1;
590 return NULL;
591 } else {
592 if (n == max) {
593 max += 1024;
594 line = grow_array(char, max, line);
595 }
596 if (c != '\n') {
597 line[n++] = c;
598 } else {
599 line[n++] = 0;
600 return line;
601 }
602 }
603 } while (c != '\n');
604 assert(0);
605}
606/*}}}*/
607static void interactive_stdio(void)/*{{{*/
608{
609 char *line = NULL;
610 int at_eof;
611
612 do {
613 line = get_line_stdio("tdl> ", &at_eof);
614 if (!at_eof) {
615 split_line_and_dispatch(line);
616 free(line);
617 }
618 } while (!at_eof);
619}
620/*}}}*/
621static char *interactive_text_stdio(char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
622{
623 char *line;
624 int at_eof;
625 *error = 0;
626 line = get_line_stdio(prompt, &at_eof);
627 *is_blank = line ? is_line_blank(line) : 0;
628 return line;
629}
630/*}}}*/
631char *interactive_text (char *prompt, char *initval, int *is_blank, int *error)/*{{{*/
632{
633#ifdef USE_READLINE
634 if (isatty(0)) {
635 char *result;
636 rl_attempted_completion_function = (CPPFunction *) null_tdl_completion;
637 result = interactive_text_readline(prompt, initval, is_blank, error);
638 rl_attempted_completion_function = (CPPFunction *) tdl_completion;
639 return result;
640 } else {
641 /* In case someone wants to drive tdl from a script, by redirecting stdin to it. */
642 return interactive_text_stdio(prompt, initval, is_blank, error);
643 }
644#else
645 return interactive_text_stdio(prompt, initval, is_blank, error);
646#endif
647}
648/*}}}*/
649
650void interactive(void)/*{{{*/
651{
652 /* Main interactive function */
653#ifdef USE_READLINE
654 if (isatty(0)) {
655 rl_completion_entry_function = NULL;
656 rl_attempted_completion_function = (CPPFunction *) tdl_completion;
657 interactive_readline();
658 } else {
659 /* In case someone wants to drive tdl from a script, by redirecting stdin to it. */
660 interactive_stdio();
661 }
662#else
663 interactive_stdio();
664#endif
665 if (argv) free(argv);
666 return;
667}
668
669/*}}}*/
670
671/*{{{ Main routine [for testing]*/
672#ifdef TEST
673int main (int argc, char **argv) {
674 interactive();
675 return 0;
676}
677#endif
678/*}}}*/
This page took 0.06781 seconds and 4 git commands to generate.